@appaflytech/zeyrek-ai-mcp 0.0.45 → 0.0.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -374,7 +374,7 @@ Kompleks projede kimliği platform yönetir; uygulamanın kendi kimlik middlewar
374
374
  | `POST …/auth/refresh` | yenileme çerezi (ya da gövdede `refreshToken`) → yeni jetonlar; eski yenileme jetonu tekrar gelirse bütün aile iptal, 401 |
375
375
  | `POST …/auth/sign-out` | çerezleri siler |
376
376
  | `GET …/auth/me` | kullanıcı, roller, pozisyonlar, birimler, şirketler |
377
- | `GET /{proje}/runtime/{app}/access` | izin özeti: `mode`, izinli sayfa ve uç slug'ları, entity başına eylemler ve alan düzeyleri (arayüzde gizleme için; `enforce` dışında her şey açık döner) |
377
+ | `GET /{proje}/runtime/{app}/access` | izin özeti: `mode`, izinli sayfa ve uç slug'ları, entity başına eylemler ve alan düzeyleri (arayüzde gizleme için; `enforce` dışında her şey açık döner — basit projede `off` ve listeler boş, kompleks projede `off`/`shadow` her şeyi listeler). **Runtime basit projede bu ucu çağırmaz:** tür sayfa yanıtının `project.accessModel`'inde ve `uiActions` yanıtının `accessModel`'inde gelir; özet istemcide kurulur (ADR 0021) |
378
378
 
379
379
  - Jeton önce çerezden, yoksa `Authorization: Bearer`'dan okunur. Çerezle gelen **durum değiştiren**
380
380
  istek `X-ZY-CSRF` başlığında `ZY_Csrf_{proje}` değerini ister (yoksa `403 CsrfTokenMismatch`);
@@ -111,7 +111,7 @@ dalından devam eder.
111
111
  | `setComponentProperty(name, key, value)` / `getComponentProperty(name, key)` | fonksiyon alternatifi |
112
112
  | `resetForm(name)` | Formu sıfırlar: alanlar başlangıç değerine döner, form "gönderilmemiş" olur, doğrulama mesajları silinir. Kayıt sonrası "yeni kayıt" için alanları tek tek `null`'a çekme — gönderilmiş formda her biri "zorunlu alan" mesajı çıkarır. Ad ya da komponent değişkeni alır: `resetForm('risk_form')` / `resetForm(risk_form)`. **Yalnız UI flow kodunda** |
113
113
  | `LocalStorage` · `Widget` · `params` | yerel veri · widget bağlamı · olay parametreleri. `LocalStorage` barındırıcının deposudur, salt okunur ve değerler METİNdir: web'de uygulamanın kökenindeki `localStorage` (Studio önizlemesinde Studio'nun kendi anahtarları da görünür), cihazda akışın `storage` deposu. `storage` adımı düz bir anahtara yazınca `LocalStorage.<anahtar>` aynı akışta yeni değeri verir ve sayfanın bağları yeniden değerlendirilir (`isSelected: {{ LocalStorage['zeyrek:theme-mode'] === 'dark' }}`). `state.page\|app.<key>`'e yazan adım `LocalStorage`'a girmez (ARC-147, 2026-09-29) |
114
- | `Device` | Salt okunur cihaz durumu, barındırıcı günceller: `Device.isOnline` — ağ bağlantısı var mı (bilinmiyorsa `true`). Değişince sayfanın bütün bağları yeniden değerlendirilir; çevrimdışı bandı: `isHidden: {{ Device.isOnline }}`. Cihazda `expo-network`, web'de tarayıcının `online`/`offline` olayları. `Device.biometric = { available, enrolled, kinds }` (kök ADR 0021): `available` cihaz kullanıcıyı şu an doğrulayabilir (biyometri **ya da** cihaz parolası kurulu), `enrolled` biyometri kayıtlı, `kinds` ⊆ `face · fingerprint · iris`; barındırıcı vermezse (web, derleme taşımıyor) `{ available: false, enrolled: false, kinds: [] }`, Studio önizlemesi ve kanvas senaryosu `{ true, true, ['face'] }`. Uygulama ön plana dönünce yeniden okunur. `Device` adında komponent ifade bağlamından süzülür (ayrılmış ad) |
114
+ | `Device` | Salt okunur cihaz durumu, barındırıcı günceller: `Device.isOnline` — ağ bağlantısı var mı (bilinmiyorsa `true`). Değişince sayfanın bütün bağları yeniden değerlendirilir; çevrimdışı bandı: `isHidden: {{ Device.isOnline }}`. Cihazda `expo-network`, web'de tarayıcının `online`/`offline` olayları. `Device.biometric = { available, enrolled, kinds }` (kök ADR 0021): `available` cihaz kullanıcıyı şu an doğrulayabilir (biyometri **ya da** cihaz parolası kurulu), `enrolled` biyometri kayıtlı, `kinds` ⊆ `face · fingerprint · iris`; barındırıcı vermezse (web, derleme taşımıyor) `{ available: false, enrolled: false, kinds: [] }`, Studio önizlemesi ve kanvas senaryosu `{ true, true, ['face'] }`. Uygulama ön plana dönünce yeniden okunur. Ağ yokken okumaların ne döndüğü (`Steps.<ad>.cache.at`, `error.network`): `platform_guide('page')` §Kural 1g-4. `Device.offline` (yalnız cihazda): `{ ready, total }` — uygulamanın kaç ekranı çevrimdışı açılmaya hazır; paket inmeden ve web/önizlemede yoktur (`Device.offline?.ready`); servis yazması kuyruğu varsa `queue: { pending, rejected }` ve `rejected` (reddedilen kayıtlar) da buradadır. `Device` adında komponent ifade bağlamından süzülür (ayrılmış ad) |
115
115
  | **Ayrılmış adlar** | Bu tablodaki globallerle (ve önsözün iç adlarıyla: `payload`, `error`, `repeaterId`, `_` ile başlayanlar…) aynı `name`'i taşıyan komponent (runtime yalnız İLK `-`'yi siler: `ite-m` da `item` sayılır) ifadede kendi adıyla GÖRÜNMEZ — süzülür, uyarı loglanır (liste: `@ng-apps/flow` `EXPRESSION_PRELUDE_NAMES`). Eskiden aynı ad sayfanın BÜTÜN ifadelerini `SyntaxError`'la düşürürdü (ARC-143). Gateway artık bu adların HEPSİNİ **PAGE-009** ile sayfa/layout yazma yollarında ve `page_schema_validate`'te istek gitmeden reddeder — komponente `t`, `state`, `event`, `item`, `self`, `Page`, `Row` gibi ad verme |
116
116
  | `t(key)` | Çeviri anahtarını çözer: aktif dil → projenin varsayılan dili → anahtarın kendisi. Boş string de "eksik" sayılır. Sözlük `machine.context.state.translations`'tan gelir; dil `language-selector` ile değişince bir sonraki çağrı yeni dili görür. **Yalnız UI flow kodunda vardır** — backend ifadelerinde karşılığı `Loc()`'tur (`platform_guide('workflow')`) | `t('home.welcome')` |
117
117
  | `log(...)` | console (proxy'leri temizler) |
@@ -36,7 +36,10 @@ Motorun kendi adımları (`setter`, `condition`, `microservice`, `code`, `functi
36
36
  Bildirim adımları paletten yalnız mobil uygulamada sunulur. Tarif — S04 "her gün hatırlat": önce
37
37
  `notification-permission` (S03 düğmesi), sonra `timepicker`'ın değeriyle `notification-schedule`
38
38
  (`value: "<saat>.value"`, değer `HH:mm`) ve `error` dalında kullanıcıya mesaj. Çevrimdışı bandı adım değil
39
- ifadedir: `isHidden: {{ Device.isOnline }}` (`platform_guide('expressions')`).
39
+ ifadedir: `isHidden: {{ Device.isOnline }}` (`platform_guide('expressions')`) — ağı CANLI izler, ekran açıkken bağlantı
40
+ gidince çıkar. Kabuğun kendi bandı ayrıdır: yalnız sayfa isteği (açılış ya da yenileme) düşünce çıkar — ağ yoksa
41
+ "Çevrimdışı · son güncelleme …", sunucu 5xx dönerse "Yenilenemedi". İkisi aynı anda görünebilir; sayfa bandını
42
+ ekranın tepesine değil, anlattığı verinin yanına koyun (`platform_guide('page')` §Kural 1g-4).
40
43
 
41
44
  ## İzin tarifi
42
45
 
@@ -562,3 +562,7 @@ yaptıkları. Derlenen uygulama
562
562
  Web'in `theme-toggle`'ı mobilde yoktur. Seçili düğme saklanan seçimi `LocalStorage`'dan okur:
563
563
  `{{ LocalStorage['zeyrek:theme-mode'] || 'light' }}` (ARC-147).
564
564
  - Dar ekran kırılımı (`hideOn: 'mobile'`) mobil uygulamada anlamsızdır: ekran zaten tek boydur.
565
+ - Ağ yokken ekranlar ve (açıksa) son okunan veri cihazdaki kopyadan gelir: `properties.mobile.offline`
566
+ (MOB-033), `Steps.<ad>.cache.at`, `error.network` — `platform_guide('page')` §Kural 1g-4. Önizleme önbellek kullanmaz.
567
+ "Uygulama ağsızken de çalışsın" isteğinin tarifi (okuma GET, yazma kuyruğu, liste birleştirme, düğme kilidi, korumasız
568
+ açılış, bant kapalı): §Kural 1g-4 "Ağsızken fark göstermeyen uygulama", örnek `ngapps://examples/mobile-offline-seamless`.
@@ -443,6 +443,8 @@ göstermek error flow'un işidir.
443
443
  > | `Steps.<adımAdı>.error.message` | Patlayan adımın KENDİ storage'ında ucun mesajı — `error` dalında da `always`'te de okunur, sıra aynı: `err.reason` → HTTP durum metni → `'Service request failed'` | `microservice.ts` `.catch` |
444
444
  > | `Steps.<adımAdı>.error.status` | HTTP durum KODU (sayı) — parametre ifadesi patladıysa `null` | `microservice.ts` `.catch` |
445
445
  > | `Steps.<adımAdı>.error.body` | Ucun hata gövdesi — eski `Steps.<adımAdı>.data`'nın taşındığı yer | `microservice.ts` `.catch` |
446
+ > | `Steps.<adımAdı>.error.network` | **Yalnız mobil, veri önbelleği açıkken:** yanıt hiç alınamadı (bağlantı yok; süre sınırı yalnız saklanan okumalarda, 8 sn) — `true`; `status` yine 503. Önbellek kapalıyken ve web'de alan yoktur (Kural 1g-4) | `microservice.ts` `.catch` |
447
+ > | `Steps.<adımAdı>.cache.at` | Hata değil, BAŞARI şekli: yanıt cihazdaki kayıttan geldi, `at` saklandığı an (ms). Ağdan gelen yanıtta `cache` yoktur (Kural 1g-4) | `microservice.ts` `.then` |
446
448
  > | `Steps.<adımAdı>.data` | **HER ZAMAN `null`**'dır — başarı gövdesi değil, hiçbir gövde değil (eskiden hata gövdesiydi) | `microservice.ts` `.catch` |
447
449
  >
448
450
  > **`alert` adımının `message`'ı ve `description`'ı METİN ŞABLONUDUR — yalnız `{{ }}` içi çözülür.**
@@ -1507,6 +1509,115 @@ Liste → ayrıntı, üç adım:
1507
1509
 
1508
1510
  Web uygulamasında alan yoktur.
1509
1511
 
1512
+ ## Kural 1g-4 — Mobil çevrimdışı: ayar, önbellek işareti, ağ hatası
1513
+
1514
+ Mobil uygulama ağ yokken iki şeyi cihazdaki kopyadan açar. Hangisinin açık olduğu uygulamanın ayarıdır:
1515
+ `application_update` `properties.mobile.offline` (`properties.mobile.tabs`'in yanındaki anahtar). Gövde REPLACE'tir:
1516
+ `application_get`'teki `properties`'in TAMAMI aynen gönderilir, yalnız `offline` eklenir — eksik anahtar silinir. Studio'da Nav Bar panelinin "Çevrimdışı" bölümü aynı alanı yazar.
1517
+
1518
+ | Alan | Değer | Varsayılan | Ne saklanır |
1519
+ |---|---|---|---|
1520
+ | `screens` | boolean | `true` | Ekran tanımları: açılan ekranların kopyası + uygulama paketi (korumasız ekranların gövdesi hiç açılmadan iner, korumalı ekranlar oturumluya ön yüklenir). Ağ yokken ekran kopyadan çizilir; sayfa isteği düşünce kabuk üstte "Çevrimdışı · son güncelleme …" bandını kendisi gösterir. Hazırlık: `Device.offline` |
1521
+ | `data` | boolean | `false` | Son okunan veri: `isRaw: false` YAZILMIŞ GET `microservice` adımı ve connectData okumaları (`list`, `describe`, `lookup`). `isRaw` yazılmamış ya da `true` olan adım ve dosya/akış sonucu dönen servis saklanmaz |
1522
+ | `maxAgeDays` | `1` · `7` · `30` | `7` | İkisinin de kayıt ömrü |
1523
+ | `banner` | boolean | `true` | Kabuğun çevrimdışı bandı ("Çevrimdışı · son güncelleme", "Yenilenemedi"). `false`: uygulama ağsızken fark göstermez — kendi uyarılarını da kaldırmak yazarın işidir (kök ADR 0026). Saklama ve kuyruk değişmez |
1524
+
1525
+ Yalnız varsayılandan ayrılan alanı yazın (`{ data: true }` yeter). Backend alanı doğrulamaz; cihaz okuyamadığı değeri
1526
+ (nesne olmayan ayar, boolean olmayan `screens`/`data`/`banner`, kümede olmayan ya da metin `maxAgeDays`, tanınmayan anahtar)
1527
+ SESSİZCE varsayılana düşürür — bunu söyleyen tek yer `application_create` / `application_update` yanıtındaki
1528
+ `platformWarnings`'tir (**MOB-033**).
1529
+
1530
+ **Saklanmayan:** yazma servisleri (POST/PUT/DELETE) ve connectData yazmaları — önbellekten yanıt almazlar.
1531
+ Kayıtlar kullanıcıya, ortama ve veri diline bağlıdır; çıkışta ve kullanıcı değişince silinir.
1532
+
1533
+ **Yazma kuyruğu (kök ADR 0022) — iki taraf da izin vermeli:**
1534
+
1535
+ - **Servis:** tanımında `offlineWritable: true` (`microservice_patch` `/offlineWritable`). Anlamı: "aynı istek iki kez
1536
+ gelirse bir kez uygulanır, çakışmada son yazan kazanır". Platform yazmayı sınıflamaz — iş akışı kaynaklı servisin
1537
+ yazıp yazmadığını bildirimi veren söyler. Okuyan uca (tablo `get`/`count`, görünüm kaynağı) verilirse **MS-025** uyarır.
1538
+ - **Akış:** `microservice` adımına `queued` dalı (yalnız mobil). Ağ hatasında (yanıt hiç yok) dal bağlıysa, yöntem
1539
+ yazansa (POST/PUT/DELETE) ve dosya parametresi yoksa kayıt kuyruğa girer ve `queued` koşar; `Steps.<ad>.data` `null`,
1540
+ `Steps.<ad>.queued = { clientRequestId, at }`. Dal bağlı değilse adım bugünkü gibi `error`'a gider; dosya
1541
+ parametresinde `Steps.<ad>.error.code = 'fileNotQueued'`. HTTP hatası — önündeki vekil sunucunun 502/503'ü dahil —
1542
+ kuyruğa girmez. Web uygulamasında ya da bildirimsiz servise bağlanan `queued` için yazım **FLOW-043** uyarısı alır.
1543
+ - **Sunucu karar verir:** cihaz servisin bildirimini bilmez. `queued` dalı bağlı yazmanın İLK isteği de
1544
+ `Idempotency-Key` taşır ve kuyruğa aynı kimlikle girer; aynı anahtar bir kez uygulanır, ikincisi 200
1545
+ `{ code: "Duplicate" }` alır — yanıtı yolda kaybolan yazma iki kez uygulanmaz. Bildirimsiz servis anahtarlı isteği
1546
+ 422 `OfflineReplayNotAllowedException` ile reddeder: `queued` bağlı adım çevrimiçiyken de HER ÇAĞRIDA `error`'a gider.
1547
+ FLOW-043 uyarısını bu yüzden geçiştirmeyin.
1548
+ - **Sınır:** kuyruktaki kayda bağlı ikinci işlem (yarat → sonra güncelle) yoktur; dönen kimliği bekleyen adım
1549
+ (`Steps.<ad>.data.id` ile yönlendirme, bildirim kurma) çevrimdışıyken koşamaz — o akışa `queued` dalı vermeyin.
1550
+ - **Gönderim:** bağlantı dönünce, uygulama ön plana dönünce ve ağla soğuk açılışta; kuyruğa giriş sırasıyla tek tek.
1551
+ 4xx kaydı reddeder ve sıra sürer; oturum (401/403), 408, 429, 5xx ve ağ hatası sırayı durdurur. 7 günde
1552
+ gönderilemeyen kayıt `expired` ile reddedilir. Kayıtlar kullanıcıya bağlıdır, çıkışta silinmez. Ağ açık ama
1553
+ sunucu ulaşılmazken bağlantı dönüşü tetiği gelmez — beklemesini istemediğiniz akışta `Page.offline.sync()` çağırın.
1554
+ - **Görünürlük:** kaydın sonucu akışa dönmez. `Device.offline.queue = { pending, rejected }` ve
1555
+ `Device.offline.rejected` (`id`, `service`, `at`, `status`, `code`, `message`); kod adımında `Page.offline.list()`,
1556
+ `.count()`, `.sync()`, `.resolve(id, 'retry' | 'discard')`. Süresi dolan kayıt yeniden denenmez.
1557
+ - **Önizleme:** Studio ve web'de kuyruk yoktur, `queued` hiç koşmaz; `page_screenshot` ağı kesemez. Açık ekran ilk
1558
+ çizildiği kopyayla kalır — akışa eklenen dal ekran bir sonraki açılışta etkindir.
1559
+
1560
+ Örnek: `ngapps://examples/mobile-offline-queue` (doz işaretleme: servis bildirimi + `queued` dalı).
1561
+
1562
+ **Veri önbelleği açıkken akış ne görür:**
1563
+
1564
+ - Saklanan okuma 8 sn süre sınırı taşır; yanıt gelirse olduğu gibi döner ve arkada saklanır. Saklanmayan isteklerde
1565
+ (yazmalar dahil) süre sınırı YOKTUR: yanıt vermeyen sunucuda adım bekler.
1566
+ - Yanıt gelmezse (bağlantı yok, süre doldu) ve kayıt varsa adım **başarı yolundan** sürer; storage'ı
1567
+ `{ data, headers, error: null, cache: { at } }` olur — `at` kaydın saklandığı an (ms). Ağdan gelen yanıtta `cache`
1568
+ alanı YOKTUR: "son güncelleme" satırını `Steps.<ad>.cache`'ten kurun, ağ varken gizli kalır.
1569
+ - Kayıt da yoksa adım hata yoluna gider: `Steps.<ad>.error.network` `true`, `status` 503. "Bağlantı yok" demek için
1570
+ **önce `error.network`'e bakın** — 503'ü sunucu da döner (bakım, aşırı yük); sunucu arızası
1571
+ `status == 503 && !error.network`'tür. Akış 503'ü `network`'süz karşılaştırıyorsa yazım **MOB-033** bilgi notu alır.
1572
+ Bağlantı yoksa `network` her istekte yazılır (saklanmayanlarda da); önbellek kapalıyken hiç yazılmaz.
1573
+ - connectData bağı kayıttan dolarsa düğüme `cachedAt` yazar (`{{ <öğe>.cachedAt }}`; ağdan gelince `null`), yükleme ve
1574
+ hata görünümü çıkmaz; bağlantı dönünce bağ sunucudan yeniden okunur. Kayıttan dönen `lookup` sonucu `cachedAt` taşır.
1575
+ - **Önbellek son başarılı yanıtı aynen verir.** Her çağrıda değişen bir GET (sayaç, saat, rastgele değer) ağ yokken
1576
+ eski değerini döner. Parametre, sayfa, arama ve süzgeç isteğin parçasıdır: başka parametreli çağrı başka kayıttır,
1577
+ hiç açılmamış sayfa ağ yokken gelmez.
1578
+
1579
+ **Doğrulama sınırı:** önizleme (`/dev`, Studio kanvası) ve `page_screenshot` önbellek KULLANMAZ — ayar açık olsa da her
1580
+ okuma ağdan gider, `cache` ve `cachedAt` hiç yazılmaz. `page_screenshot` ağı kesemez; çevrimdışı davranış yalnız
1581
+ cihazda görülür (uçak modu). Ölçüldü 2026-10-20 (simülatör, mağaza kabuğu): GET servis okuması ağ varken 149 ms'de
1582
+ ağdan, sunucu yanıt vermezken 8,03 sn'de kesilip kayıttan döndü.
1583
+
1584
+ Örnek: `ngapps://examples/mobile-offline-list` (liste + "son güncelleme" satırı + bağlantı yok uyarısı). Web
1585
+ uygulamasında alan yoktur; gateway web profilinde bu konuda bulgu üretmez.
1586
+
1587
+ ### Ağsızken fark göstermeyen uygulama (tarif)
1588
+
1589
+ Kullanıcı "uygulama internet yokken de normal çalışsın" dediğinde hedef: ağsızken ekran, uyarılar ve akış çevrimiçiyle
1590
+ AYNI; veri bağlantı gelince Zeyrek'e kendiliğinden gider. Altı adım birlikte kurulur — biri eksikse fark görünür
1591
+ (iPhone'da uçak kipiyle ölçüldü 2026-10-22, `mobile-offline-seamless` örneğinin kaynağı):
1592
+
1593
+ 1. **Ayar:** `properties.mobile.offline = { data: true, banner: false }` — veri önbelleği açık, kabuğun
1594
+ "Çevrimdışı · son güncelleme" bandı kapalı (kök ADR 0026).
1595
+ 2. **Okuma GET:** okuyan uç `httpMethod: 'get'`, adım `isRaw: false`; uç filtreleri ve adım parametreleri
1596
+ `source: 'query-string'` (GET'te gövde gitmez). Önbellek yalnız GET okumayı saklar; POST ile okuyan adım ağsızken
1597
+ kayıttan dönemez ve hata yoluna düşer — **FLOW-044** uyarır (yöntem servis tanımındandır, adıma yazılan saklanmaz).
1598
+ (Ölçüm: POST okuyan bir uygulamanın listesi ağsızken "yüklenemedi" dedi.)
1599
+ 3. **Yazma kuyruğu, uyarısız:** yazan uç `offlineWritable: true`; `microservice` adımının `queued` dalı `always` ile
1600
+ **aynı adıma** bağlanır — ağsız yazma da çevrimiçi gibi biter. Adımın `error` dalı çevrimiçi reddi uyarır; kuyruktaki
1601
+ kayıt sonradan reddedilirse (4xx, 7 günde `expired`) bu akışa DÖNMEZ — `Device.offline.rejected` ile gösterin,
1602
+ `Page.offline.resolve(id, 'retry' | 'discard')` ile karar verdirin; göstermemek bilinçli bir ödünleşimdir.
1603
+ 4. **Liste bekleyenleri gösterir:** yükleme akışı sunucunun yanıtına `await Page.offline.list()`'teki
1604
+ `status === 'pending'` kayıtları katar (`service` yazma ucunun slug'ı, alan değeri `parameters[].value`'dan); ağ
1605
+ yokluğu hata metni göstermez (`Steps.<ad>.error && !Steps.<ad>.error.network`).
1606
+ 5. **Düğme kilidi:** yazan düğme istek süresince `isLoading` + `isDisabled`; her dalın sonunda açılır. Kilitsiz düğmede
1607
+ hızlı ikinci dokunuş yeni bir istek ve yeni bir `Idempotency-Key`'dir — sunucu ikisini de uygular (ölçüm: 1,6 sn'de
1608
+ üç dokunuş, üç kayıt).
1609
+ 6. **Açılış ve yenileme (geçici çareler):** açılış sayfası korumasız giriş ekranıdır; kalıcı `state.app` oturum işareti
1610
+ (`storage: 'device'`) varsa mount akışı listeye `presentation: 'replace'` ile geçer, giriş yazar, çıkış siler —
1611
+ korumalı açılış sayfası ağsız ilk açılışta çizilemiyor (kök ADR 0024 taslak) ve cihazda kabuk bilgisi yokken
1612
+ middleware'in reddi girişe yönlenmiyor (zeyrek-frontend `docs/issues.md` ARC-342). Liste `scroll-view` `refreshable` +
1613
+ `refresh` olayı yükleme akışına bağlıdır — bağlantı dönünce açık ekranın mount akışı kendiliğinden koşmaz (kök ADR
1614
+ 0025 taslak). İki karar kapanınca bu adım sadeleşir.
1615
+
1616
+ Fark yalnız üç yerde kalır: giriş (ağ ister), hiç açılmamış ekran (cihazda kopyası yok) ve bağlantı dönünce gönderilen
1617
+ kayıt — artık bekleyen değildir, önbellekteki eski listede de yoktur; liste aşağı çekilene kadar görünmez (ADR 0025). Önizleme ve
1618
+ `page_screenshot` önbellek ve kuyruk kullanmaz — davranış yalnız cihazda görülür. Tam gövde (iki ekran, beş akış, iki
1619
+ uç, ayar ve değişken): `ngapps://examples/mobile-offline-seamless`.
1620
+
1510
1621
  ## Kural 1h — Tasarım: `style` prop'u ve tema değişkenleri
1511
1622
 
1512
1623
  Her komponentin `style` prop'u vardır ve emotion `css()` bloğudur: iç seçiciler, pseudo-class'lar
@@ -351,8 +351,8 @@ yazılır ama yanıt `applicationMismatch: true` ve not taşır.
351
351
  `project_update` gerekmez; ama dil production'a da gider ve orada çeviriler deployment'la gelene
352
352
  kadar runtime varsayılan dile düşer.
353
353
  - Dosya platformda **saklanmaz**: ne dışa aktarma ne içe aktarma Sürücü'ye bir şey bırakır.
354
- - Studio'nun çeviri panelindeki JSON içe aktarma bundan farklıdır: o yalnız boşları doldurur ve
355
- projede olmayan dili atlar.
354
+ - Studio'nun çeviri panelindeki "Excel'e aktar" / "Excel'den içe aktar" düğmeleri (2026-10-21) aynı
355
+ uçları kullanır; panelin eski JSON içe / dışa aktarması kaldırıldı.
356
356
  - Sınırlar: `.xlsx` (`.xls`, csv, json reddedilir), 10 MB, 10.000 satır.
357
357
 
358
358
  ## Bilinen sınırlar
@@ -1,6 +1,6 @@
1
1
  {
2
- "hash": "c1fb21eeab36758c9d81fcd1b41ece1b456a514e7aa944c41e190727b5cdc0ed",
3
- "generatedAt": "2026-10-21T05:42:28.082Z",
2
+ "hash": "638d3ad06d25c77b52f15815d4a450310d1158b5a2823627934cc7a4af6532cd",
3
+ "generatedAt": "2026-10-21T22:18:11.599Z",
4
4
  "types": 348,
5
5
  "files": 173,
6
6
  "note": "Şemaları üreten backend kaynaklarının parmak izi. Elle düzenlemeyin: 'npm run generate-schemas' yazar, 'npm run schema:check' denetler.",
@@ -117,6 +117,9 @@
117
117
  },
118
118
  "isAnonymous": {
119
119
  "type": "boolean"
120
+ },
121
+ "offlineWritable": {
122
+ "type": "boolean"
120
123
  }
121
124
  },
122
125
  "additionalProperties": false
@@ -144,6 +144,9 @@
144
144
  "isAnonymous": {
145
145
  "type": "boolean"
146
146
  },
147
+ "offlineWritable": {
148
+ "type": "boolean"
149
+ },
147
150
  "viewId": {
148
151
  "type": "string",
149
152
  "format": "uuid",
@@ -111,6 +111,9 @@
111
111
  },
112
112
  "isAnonymous": {
113
113
  "type": "boolean"
114
+ },
115
+ "offlineWritable": {
116
+ "type": "boolean"
114
117
  }
115
118
  },
116
119
  "additionalProperties": false
@@ -147,7 +147,7 @@ export function registerAccessRuntimeTools(server, platform) {
147
147
  entities: Array.isArray(data.entities) ? data.entities.length : 0
148
148
  },
149
149
  note: data.mode === 'off'
150
- ? "Zorlama KAPALI: harita her şeyi açık gösterir çünkü karar motoru çalışmıyor. Gerçek kapsamı görmek için environment'ı shadow/enforce'a alın (access_enforcement_mode_set)."
150
+ ? "Zorlama KAPALI: karar motoru çalışmıyor, her şey açıktır. Kompleks projede listeler her şeyi sayar; basit projede listeler BOŞ döner (basit proje hep off'tur, kapsam için önce kompleks'e dönüşüm gerekir — access_model_preflight). Kompleks projede gerçek kapsamı görmek için environment'ı shadow/enforce'a alın (access_enforcement_mode_set)."
151
151
  : "Harita bu kullanıcının o andaki yetkisidir; bir öğe eksikse nedenini access_preview söyler."
152
152
  };
153
153
  }));
@@ -3,6 +3,7 @@ import { handle } from '../platform/responses.js';
3
3
  import { forgetApplication, readApplicationPlatform, resolveApplication } from '../knowledge/platform/resolve.js';
4
4
  import { mobileTabsProblems, readMobileTabs } from '../knowledge/platform/mobileTabs.js';
5
5
  import { mobileTransitionProblems } from '../knowledge/platform/pageTransition.js';
6
+ import { mobileOfflineProblems } from '../knowledge/platform/mobileOffline.js';
6
7
  import { findingLine } from '../knowledge/platform/profile.js';
7
8
  import { listComponents } from './components.js';
8
9
  import { z } from 'zod';
@@ -51,6 +52,19 @@ export function mobileTransitionWarnings(properties) {
51
52
  guide: "platform_guide('page') §Kural 1g-2"
52
53
  }));
53
54
  }
55
+ /**
56
+ * MOB-033 (çevrimdışı ayarı) — `properties.mobile.offline` yazan mobil uygulama çağrısının denetimi. Yazım platforma
57
+ * YİNE gider (backend `properties`'i doğrulamaz); cihaz okuyamadığı değeri sessizce varsayılana düşürdüğü için bulgunun
58
+ * söylendiği tek yer `platformWarnings`'tir.
59
+ */
60
+ export function mobileOfflineWarnings(properties) {
61
+ return mobileOfflineProblems(properties).map(message => findingLine({
62
+ code: 'MOB-033',
63
+ severity: 'warn',
64
+ message,
65
+ guide: "platform_guide('page') §Kural 1g-4"
66
+ }));
67
+ }
54
68
  function withPlatformWarnings(response, warnings) {
55
69
  if (warnings.length === 0 || !response || typeof response !== 'object' || Array.isArray(response))
56
70
  return response;
@@ -91,7 +105,7 @@ export function registerApplicationTools(server, platform) {
91
105
  annotations: writes
92
106
  }, handle(config, async ({ projectId, ...body }) => {
93
107
  const warnings = readApplicationPlatform(body.type) === 'mobile'
94
- ? [...await mobileTabsWarnings(platform, undefined, undefined, undefined, body.properties), ...mobileTransitionWarnings(body.properties)]
108
+ ? [...await mobileTabsWarnings(platform, undefined, undefined, undefined, body.properties), ...mobileTransitionWarnings(body.properties), ...mobileOfflineWarnings(body.properties)]
95
109
  : [];
96
110
  const response = await platform.org('POST', `/applications/${projectId}`, { body });
97
111
  forgetApplication(platform);
@@ -114,7 +128,7 @@ export function registerApplicationTools(server, platform) {
114
128
  }, handle(config, async ({ id, ...body }) => {
115
129
  const current = await resolveApplication(platform, id);
116
130
  const warnings = current?.platform === 'mobile'
117
- ? [...await mobileTabsWarnings(platform, id, current.projectSlug, body.homepageId, body.properties), ...mobileTransitionWarnings(body.properties)]
131
+ ? [...await mobileTabsWarnings(platform, id, current.projectSlug, body.homepageId, body.properties), ...mobileTransitionWarnings(body.properties), ...mobileOfflineWarnings(body.properties)]
118
132
  : [];
119
133
  const response = await platform.org('PUT', `/applications/${id}`, { body });
120
134
  forgetApplication(platform, id);
@@ -16,6 +16,8 @@ import { existingMatch, ifExistsParam, platformRename, withRename } from '../pla
16
16
  import { randomUUID } from 'node:crypto';
17
17
  import { flowProfileWarnings } from './profileWrite.js';
18
18
  import { enforceTextHooks } from './textHookGuard.js';
19
+ import { readMobileOffline } from '../knowledge/platform/mobileOffline.js';
20
+ import { resolveProfile } from '../knowledge/platform/resolve.js';
19
21
  import { jsonArray } from '../platform/jsonArray.js';
20
22
  import { z } from 'zod';
21
23
  /**
@@ -38,7 +40,7 @@ async function flowWriteDrops(platform, host, project, environment, applicationI
38
40
  return emptyWriteDiff();
39
41
  }
40
42
  }
41
- const stepsParam = jsonArray(z.array(z.record(z.unknown()))).describe("Adım listesi (StepManagementSchema: { stepId, name, type, kind, args, branches }). DİKKAT: branches[].nextStepId bir GUID değil, hedef adımın stepId STRING'idir; dal ADI motorun anahtarıdır ve start/setter/code/function adımlarında 'always' olmak zorundadır. Tam şablon: ngapps://examples/flow-load-table (ya da flow_get çıktısı); adım tipleri ve setter/parameters şekli platform_guide('page') Kural 1c. Kullanıcı fonksiyonu çağırmak için 'function' adımı: { type:'function', args:{ functionId:'<function_list id>', functionName:'<ad>', parameters:[{ name:'<bildirimdeki ad>', value:{ type:'static'|'property'|'storage'|'state'|'code', ref } }] } } → çıktı Steps.<adımAdı>.data (ngapps://examples/flow-call-function, platform_guide('functions')).");
43
+ const stepsParam = jsonArray(z.array(z.record(z.unknown()))).describe("Adım listesi (StepManagementSchema: { stepId, name, type, kind, args, branches }). DİKKAT: branches[].nextStepId bir GUID değil, hedef adımın stepId STRING'idir; dal ADI motorun anahtarıdır ve start/setter/code/function adımlarında 'always' olmak zorundadır. Tam şablon: ngapps://examples/flow-load-table (ya da flow_get çıktısı); adım tipleri ve setter/parameters şekli platform_guide('page') Kural 1c. Kullanıcı fonksiyonu çağırmak için 'function' adımı: { type:'function', args:{ functionId:'<function_list id>', functionName:'<ad>', parameters:[{ name:'<bildirimdeki ad>', value:{ type:'static'|'property'|'storage'|'state'|'code', ref } }] } } → çıktı Steps.<adımAdı>.data (ngapps://examples/flow-call-function, platform_guide('functions')). Mobilde çevrimdışı yazma: 'microservice' adımına 'queued' dalı, servis offlineWritable olmalı (platform_guide('page') Kural 1g-4).");
42
44
  const policyOverrideParam = z.boolean().optional();
43
45
  const patchOperationsParam = jsonArray(z.array(z.record(z.unknown())).min(1)).describe("RFC 6902 JSON Patch işlemleri; yolları flow_get çıktısıyla keşfedin. Dizi öğelerinde indeks yerine seçici kullanılabilir: '/steps[stepId=paint]/args/iteratees[element=tbl_recruiters]/mapping/source' — seçici gövde platforma gitmeden indekse çevrilir, çözülemezse istek hiç gönderilmez.");
44
46
  const flowIdParam = guid.describe("Akış id'si; şemadaki events[].value bu id'dir");
@@ -323,6 +325,80 @@ async function functionWarnings(platform, host, project, environment, applicatio
323
325
  }
324
326
  return [...warnings];
325
327
  }
328
+ /** Bir yazım boyunca servis tanımı okuyucusu: FLOW-043 ve FLOW-044 aynı servisi bir kez okur; okunamayan `undefined`. */
329
+ function serviceDefinitionReader(platform, project, environment, applicationId) {
330
+ const cache = new Map();
331
+ return microserviceId => {
332
+ if (!cache.has(microserviceId)) {
333
+ cache.set(microserviceId, platform.tenant('GET', project, `/microservices/${applicationId}/${microserviceId}`, { environment })
334
+ .then(payload => unwrap(payload))
335
+ .catch(() => undefined));
336
+ }
337
+ return cache.get(microserviceId);
338
+ };
339
+ }
340
+ /**
341
+ * FLOW-043 (kök ADR 0022, plans/mobil-cevrimdisi-calisma g-02). `microservice` adımının bağlı `queued` dalı yalnız
342
+ * mobil kabuklarda ve yalnız `offlineWritable` serviste koşar (ağ hatasında kayıt kuyruğa girer). İkisi tanımda yok:
343
+ * uygulama türü org'dan (`resolveProfile`), bildirim servis tanımından okunur. Okunamayan uygulama ya da servis
344
+ * uyarı üretmez — bilmediğimizi uyarı diye söylemeyiz.
345
+ */
346
+ async function queuedBranchWarnings(platform, applicationId, steps, errorSteps, readService) {
347
+ const queuedSteps = [...(Array.isArray(steps) ? steps : []), ...(Array.isArray(errorSteps) ? errorSteps : [])].filter(isRecord)
348
+ .filter(step => String(step.type ?? '') === 'microservice')
349
+ .filter(step => (Array.isArray(step.branches) ? step.branches : []).filter(isRecord).some(branch => String(branch.name ?? '') === 'queued' && Boolean(branch.nextStepId)));
350
+ if (queuedSteps.length === 0)
351
+ return [];
352
+ const label = (step) => String(step.stepId ?? step.name ?? '?');
353
+ const resolved = await resolveProfile(platform, applicationId);
354
+ if (resolved.known && resolved.profile.platform !== 'mobile') {
355
+ return queuedSteps.map(step => `FLOW-043: '${label(step)}' adımının 'queued' dalı bu web uygulamasında hiç koşmaz — yazma kuyruğu yalnız mobil kabuklardadır; ağ hatası 'error' dalına gider.`);
356
+ }
357
+ const warnings = [];
358
+ for (const step of queuedSteps) {
359
+ const microserviceId = step.args?.microserviceId;
360
+ const definition = typeof microserviceId === 'string' ? await readService(microserviceId) : undefined;
361
+ // Alanı hiç taşımayan tanım (alan öncesi backend imajı) bilinmiyordur, "bildirimsiz" değil.
362
+ if (!definition || typeof definition.offlineWritable !== 'boolean' || definition.offlineWritable)
363
+ continue;
364
+ warnings.push(`FLOW-043: '${label(step)}' adımının 'queued' dalı bağlı ama '${String(definition.slug ?? microserviceId)}' servisi offlineWritable değil — 'queued' dalı bağlı yazmanın ilk isteği Idempotency-Key taşır, bildirimsiz servis onu 422 OfflineReplayNotAllowedException ile reddeder: adım çevrimiçiyken de HER ÇAĞRIDA 'error' dalına gider. Servis tekrarı kaldırıyorsa microservice_patch ile '/offlineWritable' true yapın; değilse 'queued' dalını kaldırın.`);
365
+ }
366
+ return warnings;
367
+ }
368
+ /**
369
+ * FLOW-044 (plans/mobil-cevrimdisi-calisma g-03, telefon turu 2026-10-22). Veri önbelleği yalnız GET okumayı saklar
370
+ * (frontend `isCacheableMethod`): okuyan uca (görünüm kaynağı ya da tablo `get`/`count` — MS-025 ile aynı tanım) GET dışı
371
+ * yöntemle giden mobil adım ağsızken kayıttan dönemez, hata yoluna düşer. Yöntem servis tanımındandır: backend adımın
372
+ * `httpMethod`'unu her okumada servisten üretir (`StepManagementSchema`), adıma yazılan saklanmaz. Veri önbelleği kapalı
373
+ * uygulamada hiçbir okuma saklanmadığı için susar. İş akışı ve sorgu kaynaklı uç okuma mı yazma mı bilinmez (kök ADR 0022
374
+ * Y1) — susar; okunamayan uygulama ya da servis de uyarı üretmez.
375
+ */
376
+ async function uncachedReadWarnings(platform, applicationId, steps, errorSteps, readService) {
377
+ const serviceSteps = [...(Array.isArray(steps) ? steps : []), ...(Array.isArray(errorSteps) ? errorSteps : [])].filter(isRecord)
378
+ .filter(step => String(step.type ?? '') === 'microservice')
379
+ .filter(step => typeof step.args?.microserviceId === 'string');
380
+ if (serviceSteps.length === 0)
381
+ return [];
382
+ const resolved = await resolveProfile(platform, applicationId);
383
+ if (!resolved.known || resolved.profile.platform !== 'mobile' || !readMobileOffline(resolved.application?.properties).data)
384
+ return [];
385
+ const warnings = [];
386
+ for (const step of serviceSteps) {
387
+ const args = step.args;
388
+ const definition = await readService(args.microserviceId);
389
+ if (!definition)
390
+ continue;
391
+ const source = String(definition.source ?? '').toLowerCase();
392
+ const operation = String(definition.sourceParameters?.operation ?? '').toLowerCase();
393
+ if (source !== 'view' && !(source === 'table' && (operation === 'get' || operation === 'count')))
394
+ continue;
395
+ const method = String(definition.httpMethod ?? 'get').toLowerCase();
396
+ if (method === 'get')
397
+ continue;
398
+ warnings.push(`FLOW-044: '${String(step.stepId ?? step.name ?? '?')}' adımı '${String(definition.slug ?? args.microserviceId)}' servisinden ${method.toUpperCase()} ile okuyor — veri önbelleği yalnız GET okumayı saklar: ağsızken bu adım kayıttan dönemez, hata yoluna düşer. Okuyan ucu GET yapın (microservice_patch '/httpMethod' 'get'; adımın yöntemi servisten gelir) ve gövdeden okunan parametreleri query-string'e taşıyın — uç filtrelerinin ve adım parametrelerinin source'u 'query-string' olmalı, GET'te gövde gitmez.`);
399
+ }
400
+ return warnings;
401
+ }
326
402
  /**
327
403
  * FLOW-038 (g-02, ADR 0004 — ZM-07). `microservice.ts` `.finally` üç yoldan birini dener: adımın
328
404
  * kendi `error` dalı bağlıysa oradan sürer, yoksa flow `errorSteps`'i (`engine.onError`) devralır,
@@ -457,12 +533,15 @@ export function registerFlowTools(server, platform) {
457
533
  hostTree: async () => hostNodes(hostRecord),
458
534
  where: `akış '${name}'`
459
535
  }, policyOverride);
536
+ const readService = serviceDefinitionReader(platform, project, environment, applicationId);
460
537
  const parameterWarnings = [
461
538
  ...await requiredParameterWarnings(platform, project, environment, applicationId, steps),
462
539
  ...await functionWarnings(platform, host, project, environment, applicationId, hostId, steps),
463
540
  ...flowBodyWarnings(steps),
464
541
  ...unknownSetterTargetWarnings(steps, errorSteps ?? [], hostNodes(hostRecord)),
465
- ...microserviceAlwaysDataWarnings(steps, errorSteps ?? [])
542
+ ...microserviceAlwaysDataWarnings(steps, errorSteps ?? []),
543
+ ...await queuedBranchWarnings(platform, applicationId, steps, errorSteps ?? [], readService),
544
+ ...await uncachedReadWarnings(platform, applicationId, steps, errorSteps ?? [], readService)
466
545
  ];
467
546
  // Gövde yalnız akıştır (designer-partial-update K1): barındırıcı zarfı platformda hâlâ isteğe bağlı
468
547
  // ama gönderilirse okunan tam kaydı yeniden yazar, açık Studio designer'ının kayıtlı tabanını
@@ -544,12 +623,15 @@ export function registerFlowTools(server, platform) {
544
623
  where: `akış '${String(flow.name ?? flowId)}'`
545
624
  }, policyOverride)
546
625
  : [];
626
+ const readService = serviceDefinitionReader(platform, project, environment, applicationId);
547
627
  const parameterWarnings = [
548
628
  ...await requiredParameterWarnings(platform, project, environment, applicationId, flow.steps),
549
629
  ...await functionWarnings(platform, host, project, environment, applicationId, hostId, flow.steps),
550
630
  ...flowBodyWarnings(flow.steps),
551
631
  ...unknownSetterTargetWarnings(flow.steps, flow.errorSteps, hostNodes(hostRecord)),
552
- ...microserviceAlwaysDataWarnings(flow.steps, flow.errorSteps)
632
+ ...microserviceAlwaysDataWarnings(flow.steps, flow.errorSteps),
633
+ ...await queuedBranchWarnings(platform, applicationId, flow.steps, flow.errorSteps, readService),
634
+ ...await uncachedReadWarnings(platform, applicationId, flow.steps, flow.errorSteps, readService)
553
635
  ];
554
636
  await platform.tenant('PUT', project, flowPath(host, applicationId, hostId, flowId), { environment, body: { flow } });
555
637
  // Kısmi güncellemede yalnız GÖNDERİLEN alanlar karşılaştırılır; verilmeyen alan için
@@ -607,12 +689,15 @@ export function registerFlowTools(server, platform) {
607
689
  where: `akış '${String(current.name ?? flowId)}'`
608
690
  }, policyOverride)
609
691
  ];
692
+ const readService = serviceDefinitionReader(platform, project, environment, applicationId);
610
693
  const parameterWarnings = [
611
694
  ...await requiredParameterWarnings(platform, project, environment, applicationId, flow.steps),
612
695
  ...await functionWarnings(platform, host, project, environment, applicationId, hostId, flow.steps),
613
696
  ...flowBodyWarnings(flow.steps),
614
697
  ...unknownSetterTargetWarnings(flow.steps, flow.errorSteps, hostNodes(hostRecord)),
615
- ...microserviceAlwaysDataWarnings(flow.steps, flow.errorSteps)
698
+ ...microserviceAlwaysDataWarnings(flow.steps, flow.errorSteps),
699
+ ...await queuedBranchWarnings(platform, applicationId, flow.steps, flow.errorSteps, readService),
700
+ ...await uncachedReadWarnings(platform, applicationId, flow.steps, flow.errorSteps, readService)
616
701
  ];
617
702
  // Gövde değil FARK gönderilir; platform PATCH ucu akışı tek başına yazar. Net diff okunan kayıt ile son hâl arasından hesaplanır.
618
703
  const netOps = fastJsonPatch.compare(current, flow);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appaflytech/zeyrek-ai-mcp",
3
- "version": "0.0.45",
3
+ "version": "0.0.46",
4
4
  "type": "module",
5
5
  "description": "Zeyrek (NgApps) platform APIs as MCP tools — data, logic and Studio design surface in one gateway",
6
6
  "license": "UNLICENSED",