@appaflytech/zeyrek-ai-mcp 0.0.52 → 0.0.53

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.
Files changed (39) hide show
  1. package/dist/knowledge/componentCatalog.js +3 -1
  2. package/dist/knowledge/componentEvents.js +13 -1
  3. package/dist/knowledge/mobileComponentCatalog.js +4 -1
  4. package/dist/knowledge/normalize.js +4 -3
  5. package/dist/platform/argumentAliases.js +13 -4
  6. package/dist/platform/bodyFile.js +195 -38
  7. package/dist/platform/capture/actions.js +24 -5
  8. package/dist/platform/capture/capture.js +80 -11
  9. package/dist/platform/capture/persistent-browser.js +23 -1
  10. package/dist/platform/capture/readiness.js +32 -1
  11. package/dist/platform/errors.js +45 -5
  12. package/dist/platform/http.js +17 -9
  13. package/dist/platform/responses.js +7 -1
  14. package/dist/platform/secrets.js +8 -0
  15. package/dist/platform/token.js +32 -21
  16. package/dist/resources/examples/mobile-offline-list.json +1 -1
  17. package/dist/resources/examples/mobile-offline-queue.json +1 -1
  18. package/dist/resources/examples/mobile-offline-seamless.json +12 -3
  19. package/dist/resources/guides/deployment.md +9 -0
  20. package/dist/resources/guides/microservice.md +13 -1
  21. package/dist/resources/guides/mobile-publish.md +4 -2
  22. package/dist/resources/guides/mobile-steps.md +40 -3
  23. package/dist/resources/guides/mobile.md +38 -8
  24. package/dist/resources/guides/page.md +39 -8
  25. package/dist/resources/guides/translation.md +3 -1
  26. package/dist/resources/schema-fingerprint.json +2 -2
  27. package/dist/tools/applications.js +7 -2
  28. package/dist/tools/deployments.js +1 -1
  29. package/dist/tools/designSchemaPatch.js +7 -2
  30. package/dist/tools/environments.js +40 -0
  31. package/dist/tools/flows.js +30 -3
  32. package/dist/tools/mobile.js +56 -0
  33. package/dist/tools/mobileStore.js +1 -1
  34. package/dist/tools/pages.js +45 -11
  35. package/dist/tools/platformInfo.js +1 -1
  36. package/dist/tools/screenWrite.js +107 -1
  37. package/dist/tools/variables.js +1 -1
  38. package/package.json +1 -1
  39. package/dist/resources/guides/mobile-flow.md +0 -64
@@ -467,7 +467,7 @@ export function registerMobileStoreTools(server, platform, storeArt = {}) {
467
467
  }));
468
468
  server.registerTool('mobile_build_start', {
469
469
  title: 'Mobil derleme başlat',
470
- description: "Mobil uygulama için derleme işi açar (POST mobile-builds { platform, target }). Önce ön kontrolü okur (GET mobile-store/{appId}/preflight — Studio'nun Yayımla düğmesiyle aynı liste: ayar, görsel, gizli bilgi, App Store Connect / Play erişimi, production sayfası, etkin iş, çevrimiçi runner, beyanlar; iOS yükleme hedeflerinde App Review satırları P-15…P-21, yayın hedeflerinde kaynak derleme P-22); bir satır bile 'fail' ise iş BAŞLATILMAZ ve fail satırları kod + öneriyle hata olarak döner ('manual' satırlar başlatmayı engellemez, kullanıcıya söylenir). dryRun:true yalnız ön kontrolü döner, iş açmaz — yayından önce bununla bakın. Hedef–platform: build her ikisinde, testflight ve app-store-review/app-store-release yalnız ios, internal ve play-promote yalnız android. Mağaza yayın hedefleri (app-store-review, app-store-release, play-promote + releaseTrack closed|production) geri dönüşsüz ya da insan onaylıdır: YALNIZ dryRun ile kabul edilir, gerçek gönderim Studio › Yayınla'dadır; mağaza durumu ve sıradaki eylem mobile_store_get { detail: ['status'] }. packageType: aab (varsayılan) | apk — APK yalnız android build'de (mağazaya gitmez, telefona kurulur). Runner: runnerScope verilmezse backend seçer — kurumun kendi Mac'i çevrimiçiyse dedicated, değilse Zeyrek'in ortak runner'ı (shared): kurumun kendi Mac'i yoksa iş Zeyrek'in runner'ına düşer; yanıtta runnerKind own | zeyrek. runnerId işi kullanıcının o Mac'ine ayırır (runnerScope gerekmez — iş dedicated kapsamında o runner'ı bekler; kullanıcının birden çok çevrimiçi Mac'i varken işin hangisine düşeceğini sabitler): runner kurumun kendi runner'ı değilse, platformu desteklemiyorsa ya da isteyene derleyemiyorsa 404 döner, runnerScope: 'shared' ile birlikte 400 döner — ikisinde de numara ayrılmaz; çevrimdışı Mac reddedilmez, iş onu bekler. runnerId verilmezse iş eskisi gibi ayrılmaz (kapsamdaki ilk yoklayan runner alır). Bu araç Cihazda dene önizlemesini başlatmaz (önizleme görevi, kod ve davet kullanıcının Studio'daki işidir). Tuzak: her başlatma derleme numarasını AYIRIR ve ilk başlatma kimlik alanlarını kilitler; iptal ya da hata numarayı geri vermez. Aynı platformda etkin iş varsa 409. İş asenkrondur: mobile_build_status ile izleyin. Ortam: environment (ad ya da slug; boş = production) derlemenin içeriğini o ortamdan alır — development, production ya da hazır bir dal; yalnız build | testflight | internal'da verilir, yükseltme hedefinde gateway reddeder: mağazaya yalnız production işinin derlemesi gider, development/dal test derlemesi asla yükseltilemez. Ön kontrolün kapsamı scope: 'store' (varsayılan; mağazaya giden işin bütün kuralları) | 'test' (Yayınla 3 · Test sürümü: yalnız TestFlight / Play iç testin istedikleri — kimlik, hesap ve imza, konsolda uygulama kaydı, seçilen ortamda açılış sayfası, runner; metin, görsel, beyan ve App Review satırları sorulmaz); environment verilince scope 'test' olur, 'store' ile birlikte reddedilir; scope işe yazılmaz. Derlenen uygulama işin ortamını okur — yükleme hedeflerinde açılış sayfası o ortamda olmalı (P-06 fail; production'da dağıtılmış olmalı); yalın build'de P-06 yalnız uyarıdır. Yanıt işin environment'ını taşır.",
470
+ description: "Mobil uygulama için derleme işi açar (POST mobile-builds { platform, target }). Önce ön kontrolü okur (GET mobile-store/{appId}/preflight — Studio'nun Yayımla düğmesiyle aynı liste: ayar, görsel, gizli bilgi, App Store Connect / Play erişimi, production sayfası, etkin iş, çevrimiçi runner, beyanlar; iOS yükleme hedeflerinde App Review satırları P-15…P-21, yayın hedeflerinde kaynak derleme P-22); bir satır bile 'fail' ise iş BAŞLATILMAZ ve fail satırları kod + öneriyle hata olarak döner ('manual' satırlar başlatmayı engellemez, kullanıcıya söylenir). dryRun:true yalnız ön kontrolü döner, iş açmaz — yayından önce bununla bakın. Hedef–platform: build her ikisinde, testflight ve app-store-review/app-store-release yalnız ios, internal ve play-promote yalnız android. Mağaza yayın hedefleri (app-store-review, app-store-release, play-promote + releaseTrack closed|production) geri dönüşsüz ya da insan onaylıdır: YALNIZ dryRun ile kabul edilir, gerçek gönderim Studio › Yayınla'dadır; mağaza durumu ve sıradaki eylem mobile_store_get { detail: ['status'] }. packageType: aab (varsayılan) | apk — APK yalnız android build'de (mağazaya gitmez, telefona kurulur). Runner: runnerScope verilmezse backend seçer — kurumun kendi Mac'i çevrimiçiyse dedicated, değilse Zeyrek'in ortak runner'ı (shared): kurumun kendi Mac'i yoksa iş Zeyrek'in runner'ına düşer; yanıtta runnerKind own | zeyrek. runnerId işi kullanıcının o Mac'ine ayırır (runnerScope gerekmez — iş dedicated kapsamında o runner'ı bekler; kullanıcının birden çok çevrimiçi Mac'i varken işin hangisine düşeceğini sabitler): runner kurumun kendi runner'ı değilse, platformu desteklemiyorsa ya da isteyene derleyemiyorsa 404 döner, runnerScope: 'shared' ile birlikte 400 döner — ikisinde de numara ayrılmaz; çevrimdışı Mac reddedilmez, iş onu bekler. runnerId verilmezse iş eskisi gibi ayrılmaz (kapsamdaki ilk yoklayan runner alır). Bu araç Cihazda dene önizlemesini başlatmaz (önizleme görevi, kod ve davet kullanıcının Studio'daki işidir). Sayfa değişikliğini yerelde görmek için derleme gerekmez: platform_guide('mobile') §Cihazda dene → Senin Mac'inde; iOS target 'build' imzalama kimliği yokken imzasız simülatör .app'i derler (kimlik tamsa imzalı .ipa — simülatörde açılmaz). Tuzak: her başlatma derleme numarasını AYIRIR ve ilk başlatma kimlik alanlarını kilitler; iptal ya da hata numarayı geri vermez. Aynı platformda etkin iş varsa 409. İş asenkrondur: mobile_build_status ile izleyin. Ortam: environment (ad ya da slug; boş = production) derlemenin içeriğini o ortamdan alır — development, production ya da hazır bir dal; yalnız build | testflight | internal'da verilir, yükseltme hedefinde gateway reddeder: mağazaya yalnız production işinin derlemesi gider, development/dal test derlemesi asla yükseltilemez. Ön kontrolün kapsamı scope: 'store' (varsayılan; mağazaya giden işin bütün kuralları) | 'test' (Yayınla 3 · Test sürümü: yalnız TestFlight / Play iç testin istedikleri — kimlik, hesap ve imza, konsolda uygulama kaydı, seçilen ortamda açılış sayfası, runner; metin, görsel, beyan ve App Review satırları sorulmaz); environment verilince scope 'test' olur, 'store' ile birlikte reddedilir; scope işe yazılmaz. Derlenen uygulama işin ortamını okur — yükleme hedeflerinde açılış sayfası o ortamda olmalı (P-06 fail; production'da dağıtılmış olmalı); yalın build'de P-06 yalnız uyarıdır. Yanıt işin environment'ını taşır.",
471
471
  inputSchema: {
472
472
  applicationId,
473
473
  platform: mobilePlatform,
@@ -4,14 +4,14 @@ import { functionSignature } from '../knowledge/functionGuards.js';
4
4
  import { attachFindings } from '../knowledge/policy.js';
5
5
  import { fetchHost, pageUpdateBody, schemaParam, eventsParam, unwrap, modeParam, summarize, findNode, flowPath, countSchemaNodes, missingElementIds, readFullSchema } from './screens.js';
6
6
  import { fetchComponentEventNames } from './components.js';
7
- import { assertScreenWriteGuards, screenCatalogNotes, assertScreenCatalog } from './screenWrite.js';
7
+ import { assertScreenWriteGuards, screenCatalogNotes, assertScreenCatalog, unknownPropOps } from './screenWrite.js';
8
8
  import { opsParam, writeViewParam, outlineWriteResponse, unknownEventOps, collectTypes, simulateLayoutOps, applySkeletonOps, reshapesSkeleton, planEventRemovals, callerSlots, planEmulatedOps, recordsWithEventRemovals, serverOpsInOrder, rejectedOpsMessage } from './schemaOps.js';
9
9
  import { formBindingWarnings, formSubmitButtonWarnings } from '../knowledge/coverage.js';
10
10
  import { emptyWriteDiff, schemaOpDrops, unwrapProps, withWriteDiff, writeDiff } from '../platform/writeDiff.js';
11
11
  import { PlatformApiError } from '../platform/errors.js';
12
12
  import { existingMatch, ifExistsParam, listAll, withOutcome } from '../platform/ifExists.js';
13
13
  import { handle, toolResult, toolError } from '../platform/responses.js';
14
- import { expressionErrorsNote, runtimeErrorsNote, StudioScreenshotter } from '../platform/capture/index.js';
14
+ import { awaitingUserNote, expressionErrorsNote, runtimeErrorsNote, StudioScreenshotter } from '../platform/capture/index.js';
15
15
  import { captureTargetFor } from '../platform/capture/target.js';
16
16
  import { diffSummaries, summaryLines, summaryStats } from '../platform/domSummary.js';
17
17
  import { captureKey, imageHash, sharedCaptureMemory } from '../platform/screenshotMemory.js';
@@ -272,7 +272,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
272
272
  const maxInlinePngBytes = 5 * 1024 * 1024;
273
273
  server.registerTool('page_screenshot', {
274
274
  title: 'Sayfanın ekran görüntüsünü al',
275
- description: "ÖNCE KARESİZ DENE: 'kart göründü mü, tablo doldu mu, hata var mı' gibi olgu sorularında image:'none' verin — kare çekilmez, yanıttaki summaryText her kök düğümün görünürlüğünü, metnini, satır sayısını, açık sekmesini ve hata mesajlarını söyler (bir tam sayfa karesinin yüzlerce katı küçük). Kareyi yalnız görsel soru (renk, hizalama, referansa sadakat) için alın ve küçültün: clip ile bölge, scale:0.5 ile yarı çözünürlük; aynı sayfayı yeniden çekerken image:'ifChanged'. Studio arayüzünün runtime render rotasını (/dev ya da /view) headless Chromium'da açar ve sayfanın PNG ekran görüntüsünü inline görüntü olarak döner — kurduğun sayfayı GÖZLE doğrulamanın yolu budur (page_runtime_get paketi verir, bu tool ekranda ne ÇİZİLDİĞİNİ gösterir). Sayfaya bağlı middleware'ler gerçekten koşar; reddedilirse yanıtta middlewareBlocked işaretlenir ve Studio'nun çizdiği hata durumu karesi döner — bu da bir doğrulamadır. YÜKLENMEMİŞ SAYFA ASLA ÇEKİLMEZ: kare, runtime çizilip mount akışları bitince alınır (yanıtta pageState + readyAfterMs); bütçe dolduğunda sayfa hâlâ iskelette ya da akış koşuyorsa çağrı ne görüldüğünü söyleyen bir hatayla döner, boş/yarım kare dönmez. Sayfa akışı bir adımda düşerse (patlayan ifade, hata dönen uç) yanıt runtimeErrors taşır ve hangi komponentin hangi prop'unun YAZILMADIĞINI söyler — kare 'çalışıyor' görünürken sessizce boş kalan komponenti ancak bu yakalar. Bir komponent prop'unun İFADESİ patlarsa (Studio'da 'Expression error' toast'ı) yanıt expressionErrors taşır: {elementId, prop, expression, message, count}, öğe başına en çok iki; aynı satırın konsol kopyası consoleErrors'a düşmez. Sayfa ya da bir bileşen ÇİZİLEMEZSE neden yanıttadır: mobilde çöken düğüm renderErrors ({elementId, message}; sayfanın geri kalanı çizilir), yakalanmamış istisna pageErrors, konsol hatası consoleErrors — kare alınamadıysa bütün nedenler hata metninde. KARE DONUKTUR: tarayıcı bağlamı 'hareketi azalt' tercihiyle açılır; mobil runtime bileşen hareketini (giriş, döngü, geçiş, iskelet parıltısı, yükleme göstergesi) durdurur ve aynı sayfanın iki karesi tekrarlanabilir. Web sayfasında yalnız CSS'i prefers-reduced-motion'a uyan animasyonlar durur. Hareketin kendisini görmek için motion:'play'. Kare DİLİ ve TEMASI seçilebilir: 'language' adrese ?lang ekler (çeviriye bağlı prop'lar da çok dilli alanların satır değerleri de o dilde çözülür), 'colorScheme' ziyaretçinin tema tercihini sayfa açılmadan eker — ikisi de sayfaya dil değiştirici ya da theme-toggle koymayı GEREKTİRMEZ, yani referansa sadık bir tasarımın ikinci dili ve koyu teması bozulmadan kanıtlanır. STUDIO_BASE_URL tanımlı olmalı; ilk kullanım için `npx playwright-core install chromium` gerekir. DAL ORTAMI DA GÖRÜNTÜLENİR (2026-09-09'dan beri): 'development' /dev, 'production' /view, dal ise /branch/{dal} şeridinden açılır ve sayfanın BÜTÜN istekleri (şema da akışlar da) o dala gider — dalda kurulan sayfa artık merge'i beklemeden gözle doğrulanır (platform_guide('merge') §Dalda görsel doğrulama).",
275
+ description: "Telefonda (mağaza kabuğu ya da oynatıcı) düşen akış hatasını bu araç görmez — onu mobile_error_list okur. ÖNCE KARESİZ DENE: 'kart göründü mü, tablo doldu mu, hata var mı' gibi olgu sorularında image:'none' verin — kare çekilmez, yanıttaki summaryText her kök düğümün görünürlüğünü, metnini, satır sayısını, açık sekmesini ve hata mesajlarını söyler (bir tam sayfa karesinin yüzlerce katı küçük). Kareyi yalnız görsel soru (renk, hizalama, referansa sadakat) için alın ve küçültün: clip ile bölge, scale:0.5 ile yarı çözünürlük; aynı sayfayı yeniden çekerken image:'ifChanged'. Studio arayüzünün runtime render rotasını (/dev ya da /view) headless Chromium'da açar ve sayfanın PNG ekran görüntüsünü inline görüntü olarak döner — kurduğun sayfayı GÖZLE doğrulamanın yolu budur (page_runtime_get paketi verir, bu tool ekranda ne ÇİZİLDİĞİNİ gösterir). Sayfaya bağlı middleware'ler gerçekten koşar; reddedilirse yanıtta middlewareBlocked işaretlenir ve Studio'nun çizdiği hata durumu karesi döner — bu da bir doğrulamadır. YÜKLENMEMİŞ SAYFA ASLA ÇEKİLMEZ: kare, runtime çizilip mount akışları bitince alınır (yanıtta pageState + readyAfterMs); bütçe dolduğunda sayfa hâlâ iskelette ya da akış koşuyorsa çağrı ne görüldüğünü söyleyen bir hatayla döner, boş/yarım kare dönmez. Sayfa akışı bir adımda düşerse (patlayan ifade, hata dönen uç) yanıt runtimeErrors taşır ve hangi komponentin hangi prop'unun YAZILMADIĞINI söyler — kare 'çalışıyor' görünürken sessizce boş kalan komponenti ancak bu yakalar. Bir komponent prop'unun İFADESİ patlarsa (Studio'da 'Expression error' toast'ı) yanıt expressionErrors taşır: {elementId, prop, expression, message, count}, öğe başına en çok iki; aynı satırın konsol kopyası consoleErrors'a düşmez. Sayfa ya da bir bileşen ÇİZİLEMEZSE neden yanıttadır: mobilde çöken düğüm renderErrors ({elementId, message}; sayfanın geri kalanı çizilir), yakalanmamış istisna pageErrors, konsol hatası consoleErrors — kare alınamadıysa bütün nedenler hata metninde. KARE DONUKTUR: tarayıcı bağlamı 'hareketi azalt' tercihiyle açılır; mobil runtime bileşen hareketini (giriş, döngü, geçiş, iskelet parıltısı, yükleme göstergesi) durdurur ve aynı sayfanın iki karesi tekrarlanabilir. Web sayfasında yalnız CSS'i prefers-reduced-motion'a uyan animasyonlar durur. Hareketin kendisini görmek için motion:'play'. Kare DİLİ ve TEMASI seçilebilir: 'language' adrese ?lang ekler (çeviriye bağlı prop'lar da çok dilli alanların satır değerleri de o dilde çözülür), 'colorScheme' ziyaretçinin tema tercihini sayfa açılmadan eker — ikisi de sayfaya dil değiştirici ya da theme-toggle koymayı GEREKTİRMEZ, yani referansa sadık bir tasarımın ikinci dili ve koyu teması bozulmadan kanıtlanır. STUDIO_BASE_URL tanımlı olmalı; ilk kullanım için `npx playwright-core install chromium` gerekir. DAL ORTAMI DA GÖRÜNTÜLENİR (2026-09-09'dan beri): 'development' /dev, 'production' /view, dal ise /branch/{dal} şeridinden açılır ve sayfanın BÜTÜN istekleri (şema da akışlar da) o dala gider — dalda kurulan sayfa artık merge'i beklemeden gözle doğrulanır (platform_guide('merge') §Dalda görsel doğrulama).",
276
276
  inputSchema: {
277
277
  project: projectParam,
278
278
  environment: environmentParam,
@@ -300,14 +300,16 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
300
300
  layoutReport: z.boolean().optional().describe("true: kareyle birlikte her runtime düğümünün efektif piksel kutusunu ve özetini (taşan / viewport dışı / sıfır boyutlu elementId'ler) döner — taşma ve çakışmayı görüntüsüz, sayısal yakalar. Mobil uygulamada özet ayrıca layoutSummary.alignment taşır: gutter (ekran kenarı boşluğu), leftEdges (aynı kapsamdaki geniş içeriğin sol kenar kümeleri — sözleşmede tek küme), surfaces (iç hizası bozuk yüzey kapları), widthOutliers (sağ kenarı sapan geniş öğeler, içeri itilme dp'si), offScaleGaps (0/4/8/16/24/32 dışı dikey aralıklar). Yan yana dizilen, ekran dışında kalan ve akış dışı (app-bar, fab, modal…) öğeler kıyasa girmez. Düğümler data-element-id taşımıyorsa layoutReportUnavailable döner."),
301
301
  cookies: z.record(z.string()).optional().describe("Tarayıcıya enjekte edilecek EK çerezler (ad → değer): uygulamanın kendi oturum çerezi buraya verilir. Aynı ada sahip çerez gateway jetonunun yerine geçer. anonymous ile birlikte verilemez."),
302
302
  actions: z.array(z.object({
303
- action: z.enum(['set', 'click', 'wait', 'drag', 'swipe', 'tab', 'select', 'submit']),
304
- target: z.string().min(1).optional().describe("Hedefin elementId'si (page_schema_get); runtime düğümü data-element-id ile bulunur. 'drag' için sürüklenen öğe (ör. 'kb_board__card-42', 'cal_plan__event-7'); 'swipe' için jestin basıldığı öğe (ör. tutamaç 'lst__row-2__handle', satır 'lst__row-3'); 'tab' için sekme GRUBUNUN kendisi ('tabs_settings'), 'select' için açılır kutu, 'submit' için form komponenti"),
303
+ action: z.enum(['set', 'click', 'wait', 'drag', 'swipe', 'tab', 'select', 'submit', 'network']),
304
+ target: z.string().min(1).optional().describe("Hedefin elementId'si (page_schema_get); runtime düğümü data-element-id ile bulunur. 'drag' için sürüklenen öğe (ör. 'kb_board__card-42', 'cal_plan__event-7'); 'swipe' için jestin basıldığı öğe (ör. tutamaç 'lst__row-2__handle', satır 'lst__row-3'); 'tab' için sekme GRUBUNUN kendisi ('tabs_settings'), 'select' için açılır kutu, 'submit' için form komponenti. Önizlemenin kendi pencereleri de hedeftir: confirm/alert adımı 'preview-dialog__confirm' / 'preview-dialog__cancel', biometric-auth 'preview-biometric__verify' / '__cancel' — pencereyi açan eylemin raporu pageState 'awaiting-user' der"),
305
305
  to: z.string().min(1).optional().describe("'drag' ve 'swipe': bırakma hedefinin elementId'si ('swipe'ta jest hedefin içinde, hareket yönündeki kenarına 2 px kala biter — sıralamada satır tam hedefin yerine iner; dx/dy ile birlikte verilmez) — kanban kolonu '<elementId>__col-<value>', takvim günü '<elementId>__day-<yyyy-MM-dd>', tüm gün hücresi '<elementId>__allday-<yyyy-MM-dd>', saat dilimi '<elementId>__slot-<yyyy-MM-dd>T<HH>'"),
306
306
  value: z.unknown().optional().describe("'set' için yazılacak değer. Tarih kutularına ISO yazın ('2026-08-01'); çok alanlı tarih aralığında kök DEĞİL kutu hedeflenir: '<elementId>__start' / '<elementId>__end'. 'tab' için sekmenin ANAHTARI ('iot') ya da görünen metni; 'select' için seçeneğin value'su ya da görünen metni"),
307
307
  ms: z.number().int().min(0).max(15000).optional().describe("'wait' için bekleme süresi"),
308
308
  dx: z.number().optional().describe("Yalnız 'swipe': jestin yatay kayması (CSS pikseli; sola kaydırma eksi). to ile birlikte verilmez"),
309
- dy: z.number().optional().describe("Yalnız 'swipe': jestin dikey kayması (CSS pikseli; yukarı eksi). to ile birlikte verilmez")
310
- })).max(20).optional().describe("Kare ALINMADAN ÖNCE sayfada koşturulacak etkileşim adımları: [{action:'set', target:'flt_month', value:'2022-10'}, {action:'click', target:'btn_sign_in'}, {action:'tab', target:'tabs_settings', value:'iot'}, {action:'select', target:'dd_city', value:'34'}, {action:'submit', target:'frm_note'}, {action:'drag', target:'kb_board__card-42', to:'kb_board__col-Assigned'}]. 'tab' SEKME SEÇER: sekme grubunun köküne 'click' göndermek DOM sırasındaki İLK sekmeye gider ve ikinci sekme hiç açılamıyordu — tablist köküne click artık reddedilir ve bu fiile yönlendirir. Eşleme önce '<elementId>__tab-<sekme anahtarı>' kimliğiyle, o yoksa görünen metinle yapılır; actionsReport.matchedBy hangisi olduğunu, actionsReport.activeTab adımdan SONRA hangi sekmenin açık olduğunu söyler. Etkin olmayan sekmenin içeriği DOM'da YOKTUR: o sekmedeki bir düğüme yazmadan önce 'tab' adımı gerekir. 'select' açılır kutudan seçer — kutu native alan değildir, değeri yazmak hiçbir şey yapmaz, seçeneğe tıklanması gerekir; liste 3 sn'ye kadar YOKLANIR (sorgudan beslenen kutuda seçenekler geç basılır) ve actionsReport.optionsSeen + waitedMs neyin görüldüğünü söyler. Autocomplete'te 'set' metni alana yazar, 'select' metni yazıp öneri satırını GÖRÜNEN METİNLE seçer (value ile değil; eşleşme yoksa yazılan metin alanda kalır ve note bunu söyler). 'submit' formu gönder butonu OLMADAN gönderir (form.requestSubmit); actionsReport.submitVia hangi yolun kullanıldığını yazar. 'drag' HTML5 sürükle-bırak olaylarını (dragstart→dragover→drop→dragend) hedefin merkezinde atar; kanban cardMove ve takvim eventMove bununla tetiklenir. 'swipe' DOKUNMATİK jesttir (mobil): hedefin merkezine basar, 12 adımda 16 ms arayla taşır ve bırakır (pointer + mouse olayları; sıfır kayma reddedilir — dokunuş 'click'tir) — tutamaçtan sıralama {action:'swipe', target:'lst__row-2__handle', to:'lst__row-0'}, satır kaydırma eylemi {action:'swipe', target:'lst__row-1', dx:-160, dy:0}; 'drag' web'in HTML5 sürüklemesi, 'swipe' dokunmatik jest. 'set' native setter kullanır ve input+change olaylarını bubbles ile atar (düz DOM ataması framework'ün duyduğu olayı üretmez); ardından kök düğümün runtime prop'larını yeniden okur: actionsReport.propWritten değerin prop'a ULAŞIP ulaşmadığıdır ve false ise applied da false döner — DOM'a yazılmış ama bileşenin duymadığı değer 'uygulandı' sayılmaz (G-14). Her adımdan sonra yerleşme beklenir ve yanıttaki actionsReport hedefin bulunup bulunmadığını ve adımdan sonra KAÇ AĞ İSTEĞİ çıktığını söyler — 'bağ kuruldu' ile 'çalışıyor' arasındaki farkı ancak bu ayırır (G-24)."),
309
+ dy: z.number().optional().describe("Yalnız 'swipe': jestin dikey kayması (CSS pikseli; yukarı eksi). to ile birlikte verilmez"),
310
+ blur: z.boolean().optional().describe("Yalnız 'set': false verilirse odak alanda KALIR (telefonda klavye açıkken yazılan hâl) — input+change atılır, blur/focusout atılmaz; odak kaybına bağlı akış ('changed') bu adımda KOŞMAZ. Varsayılan true: gerçek kullanıcı gibi yazıp alandan çıkar")
311
+ })).max(20).optional().describe("Kare ALINMADAN ÖNCE sayfada koşturulacak etkileşim adımları: [{action:'set', target:'flt_month', value:'2022-10'}, {action:'click', target:'btn_sign_in'}, {action:'tab', target:'tabs_settings', value:'iot'}, {action:'select', target:'dd_city', value:'34'}, {action:'submit', target:'frm_note'}, {action:'drag', target:'kb_board__card-42', to:'kb_board__col-Assigned'}]. 'tab' SEKME SEÇER: sekme grubunun köküne 'click' göndermek DOM sırasındaki İLK sekmeye gider ve ikinci sekme hiç açılamıyordu — tablist köküne click artık reddedilir ve bu fiile yönlendirir. Eşleme önce '<elementId>__tab-<sekme anahtarı>' kimliğiyle, o yoksa görünen metinle yapılır; actionsReport.matchedBy hangisi olduğunu, actionsReport.activeTab adımdan SONRA hangi sekmenin açık olduğunu söyler. Etkin olmayan sekmenin içeriği DOM'da YOKTUR: o sekmedeki bir düğüme yazmadan önce 'tab' adımı gerekir. 'select' açılır kutudan seçer — kutu native alan değildir, değeri yazmak hiçbir şey yapmaz, seçeneğe tıklanması gerekir; liste 3 sn'ye kadar YOKLANIR (sorgudan beslenen kutuda seçenekler geç basılır) ve actionsReport.optionsSeen + waitedMs neyin görüldüğünü söyler. Autocomplete'te 'set' metni alana yazar, 'select' metni yazıp öneri satırını GÖRÜNEN METİNLE seçer (value ile değil; eşleşme yoksa yazılan metin alanda kalır ve note bunu söyler). 'submit' formu gönder butonu OLMADAN gönderir (form.requestSubmit); actionsReport.submitVia hangi yolun kullanıldığını yazar. 'drag' HTML5 sürükle-bırak olaylarını (dragstart→dragover→drop→dragend) hedefin merkezinde atar; kanban cardMove ve takvim eventMove bununla tetiklenir. 'swipe' DOKUNMATİK jesttir (mobil): hedefin merkezine basar, 12 adımda 16 ms arayla taşır ve bırakır (pointer + mouse olayları; sıfır kayma reddedilir — dokunuş 'click'tir) — tutamaçtan sıralama {action:'swipe', target:'lst__row-2__handle', to:'lst__row-0'}, satır kaydırma eylemi {action:'swipe', target:'lst__row-1', dx:-160, dy:0}; 'drag' web'in HTML5 sürüklemesi, 'swipe' dokunmatik jest. 'network' bağlamın AĞINI keser ya da açar ({action:'network', value:'offline'} → ağsız yaz/kaydet → {action:'network', value:'online'}): sayfanın tamamı etkilenir, hedef almaz; actionsReport.online sayfanın gördüğü navigator.onLine'dır, ağ açılınca koşan eşitleme akışları flowRuns'a yazılır. Çağrının sonunda ağ yeniden açılır; ağsız davranışı iki çağrı arasında sürdürmek için session ile depo taşınır, ağ taşınmaz. 'set' + blur:false odağı alanda bırakır (telefonda klavye açıkken). 'set' native setter kullanır ve input+change olaylarını bubbles ile atar (düz DOM ataması framework'ün duyduğu olayı üretmez); ardından kök düğümün runtime prop'larını yeniden okur: actionsReport.propWritten değerin prop'a ULAŞIP ulaşmadığıdır ve false ise applied da false döner — DOM'a yazılmış ama bileşenin duymadığı değer 'uygulandı' sayılmaz (G-14). Her adımdan sonra yerleşme beklenir ve yanıttaki actionsReport hedefin bulunup bulunmadığını ve adımdan sonra KAÇ AĞ İSTEĞİ çıktığını söyler — 'bağ kuruldu' ile 'çalışıyor' arasındaki farkı ancak bu ayırır (G-24)."),
312
+ session: z.string().regex(/^[a-z0-9-]{1,40}$/).optional().describe("Çağrılar arası tarayıcı oturumu (kısa ad, [a-z0-9-]{1,40}): verilirse çekim sonunda çerezler, localStorage ve IndexedDB gateway BELLEĞİNDE saklanır ve aynı adla gelen sonraki çekim o depoyla açılır — kalıcı state.app ve çevrimdışı servis kuyruğu ikinci çağrıda okunur. Yanıttaki sessionRestored önceki depo yüklendi mi söyler (ilk çağrıda false). Kimlik ve adres anahtara dahildir: aynı adla farklı kullanıcı (jetonun sub'ı; yenilenen jeton aynı oturumdur), asUserId, cookies, uygulama ya da ortam ayrı oturumdur. Depo okunamazsa oturum silinir ve yanıt sessionSaved: false der. Ömür 30 dk, en çok 8 oturum (en eskisi düşer); gateway yeniden başlayınca silinir. sessionStorage (güvenli depo) taşınmaz. Ağ durumu taşınmaz: her çekim ağ AÇIK başlar. Verilmezse her çekim boş depoyla başlar (bugünkü yalıtım)."),
311
313
  language: z.string().min(1).optional().describe("Kareyi BU DİLDE çeker: adrese '?lang=<kod>' eklenir ve runtime dili oradan seçer (öncelik: ?lang → tarayıcıda kayıtlı seçim → tarayıcı dilleri → projenin varsayılanı). Seçim hem çeviriye bağlı prop'ları hem çok dilli alanların satır değerlerini kapsar, yani ikinci dilin karesi için sayfaya dil değiştirici KOYMAK GEREKMEZ — referans tasarımı bozulmadan ikinci dil kanıtlanır. ISO 639-1 iki harfli kod ('tr', 'en'); bölgeli kod ('tr-TR') indirgenir ve indirgeme yanıtta bildirilir. Kod projenin languages listesinde yoksa çağrı REDDEDİLİR: runtime tanımadığı kodu yok sayıp kareyi varsayılan dilde çizerdi ve 'çevrildi' sanılan yanlış bir kare raporlanırdı. MOBİL uygulamada da geçerlidir (/preview aynı ?lang'i okur); cihazın kendisinde ?lang yoktur (platform_guide('mobile') §Çok dil)."),
312
314
  colorScheme: z.enum(['light', 'dark']).optional().describe("Kareyi açık ya da koyu temada çeker. Ziyaretçi tema tercihi çerezde değil localStorage'da yaşar ve tema store'u onu sayfa açılırken bir kez okur; gateway tercihi sayfa script'lerinden ÖNCE eker, bu yüzden sayfada 'theme-toggle' komponenti OLMASA DA koyu tema karesi alınabilir. Verilmezse projenin kendi modu çizilir."),
313
315
  motion: z.enum(['frozen', 'play']).optional().describe("Varsayılan 'frozen': bağlam 'hareketi azalt' (prefers-reduced-motion: reduce) ile açılır; mobil runtime hareketi dondurur ve kare tekrarlanabilir olur — diff'in imageUnchanged'ı ancak böyle anlamlıdır. Web sayfasında yalnız bu tercihe uyan CSS animasyonları durur. 'play': hareket oynar (giriş animasyonu, dönen gösterge, iskelet parıltısı); kare çekildiği ana bağlıdır, iki kare bayt bayt aynı çıkmayabilir ve yanıt bunu motionNote ile söyler. diff belleği iki kipi ayrı tutar."),
@@ -324,7 +326,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
324
326
  outPath: z.string().min(1).optional().describe('Verilirse PNG bu yerel yola yazılır ve inline görüntü DÖNMEZ; yalnız özet (yol, boyut) döner — büyük fullPage kareleri için')
325
327
  },
326
328
  annotations: readOnly
327
- }, async ({ project, environment, applicationSlug, pageSlug, viewport, device, fullPage, delayMs, captureUnready, anonymous, asUser, asUserId, cookies, layoutReport, actions, outPath, expectFailure, language, colorScheme, motion, summary, image, format, quality, scale, clip, diff }) => {
329
+ }, async ({ project, environment, applicationSlug, pageSlug, viewport, device, fullPage, delayMs, captureUnready, anonymous, asUser, asUserId, cookies, layoutReport, actions, outPath, expectFailure, language, colorScheme, motion, summary, image, format, quality, scale, clip, diff, session }) => {
328
330
  try {
329
331
  if (anonymous === true && (asUser !== undefined || cookies !== undefined)) {
330
332
  throw new PlatformApiError("page_screenshot: anonymous:true ile asUser/cookies birlikte verilemez — biri kimliksiz kareyi, diğeri belirli bir kimlikle çekilen kareyi ister. Anonim kare için yalnız anonymous:true, üye kimliğiyle kare için yalnız asUser (ve gerekiyorsa cookies) gönderin.");
@@ -353,6 +355,14 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
353
355
  if (stillSwipe >= 0) {
354
356
  throw new PlatformApiError(`page_screenshot: actions[${stillSwipe}] 'swipe' adımının kayması sıfır — yerinde jest bir basıştır; dokunuş için {action:'click', target:'${actions?.[stillSwipe]?.target}'} kullanın.`);
355
357
  }
358
+ const badNetwork = actions?.findIndex(step => step.action === 'network' && (step.value !== 'offline' && step.value !== 'online' || step.target !== undefined || step.to !== undefined)) ?? -1;
359
+ if (badNetwork >= 0) {
360
+ throw new PlatformApiError(`page_screenshot: actions[${badNetwork}] 'network' adımı yalnız value: 'offline' | 'online' alır, hedef almaz — bağlamın tamamının ağını keser/açar. Ör. {action:'network', value:'offline'}.`);
361
+ }
362
+ const misplacedBlur = actions?.findIndex(step => step.blur !== undefined && step.action !== 'set') ?? -1;
363
+ if (misplacedBlur >= 0) {
364
+ throw new PlatformApiError(`page_screenshot: actions[${misplacedBlur}] '${actions?.[misplacedBlur]?.action}' adımı blur almaz — odakta kalma yalnız 'set'in seçeneğidir.`);
365
+ }
356
366
  if (!config.studioBaseUrl) {
357
367
  throw new PlatformApiError("page_screenshot için STUDIO_BASE_URL tanımlı değil. Studio arayüzünün kök adresini .env'e ekleyin (yerel geliştirmede http://localhost:3000, dağıtımda nginx adresi).");
358
368
  }
@@ -439,7 +449,27 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
439
449
  ...(clip === undefined ? {} : { clip: clip }),
440
450
  ...(actions === undefined ? {} : { actions: actions }),
441
451
  ...(mobile ? { target } : {}),
442
- ...(dpr === undefined ? {} : { dpr })
452
+ ...(dpr === undefined ? {} : { dpr }),
453
+ // Oturum kimlikle ve adresle ayrılır: jetonun öznesi (`sub`; yenilenen gateway jetonu oturumu düşürmesin, yoksa
454
+ // jetonun kendisi), asUserId, çerez değerleri, proje, uygulama ve ortam anahtara hash'lenerek girer.
455
+ ...(session === undefined ? {} : {
456
+ session: `${session}|${captureKey({
457
+ identity,
458
+ principal: `${(() => {
459
+ try {
460
+ const claims = JSON.parse(Buffer.from(String(identityToken ?? '').split('.')[1] ?? '', 'base64url').toString('utf8'));
461
+ return typeof claims.sub === 'string' ? `sub:${claims.sub}` : identityToken ?? '';
462
+ }
463
+ catch {
464
+ return identityToken ?? '';
465
+ }
466
+ })()}|${asUserId ?? ''}`,
467
+ cookieValues: extraCookies.map(cookie => `${cookie.name}=${cookie.value}`),
468
+ project: projectSlug,
469
+ applicationSlug,
470
+ environment: resolvedEnvironment
471
+ })}`
472
+ })
443
473
  });
444
474
  // Fark ÖZETTEN çıkar; `summary:false` verilse de fark için toplanmıştır (yanıta girmez).
445
475
  // Anahtar KİMLİĞİ ayırır (K8): 'asUser' etiketi yetmez — iki farklı üyenin jetonu aynı
@@ -494,7 +524,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
494
524
  const emptyOptions = actionsOf(outcome => outcome.action === 'select' && outcome.optionsSeen === 0);
495
525
  const unwritten = actionsOf(outcome => outcome.propWritten === false);
496
526
  // 'wait' bir eylem değil bekleyiştir: akış sayacı ona bağlanmaz, notu tetiklemez (33/S-1).
497
- const silent = actionsOf(outcome => outcome.action !== 'wait' && outcome.applied && outcome.requests === 0 && (outcome.flowRuns ?? 0) === 0);
527
+ const silent = actionsOf(outcome => outcome.action !== 'wait' && outcome.action !== 'network' && outcome.applied && outcome.requests === 0 && (outcome.flowRuns ?? 0) === 0);
498
528
  if (missing.length > 0)
499
529
  actionNotes.push(`Bulunamayan hedef var: ${missing.map(outcome => outcome.target ?? '?').join(' · ')}. elementId'leri page_schema_get ile doğrulayın; runtime düğümü data-element-id ile bulunuyor.`);
500
530
  if (textMatchedTabs.length > 0)
@@ -521,6 +551,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
521
551
  fullPage: fullPage === true,
522
552
  identity,
523
553
  cookieNames,
554
+ ...(session === undefined ? {} : { session, sessionRestored: shot.sessionRestored === true, ...(shot.sessionSaved === false ? { sessionSaved: false, sessionNote: 'Depo durumu okunamadı: oturum SİLİNDİ, aynı adla sonraki çekim boş depoyla başlar (sessionRestored: false).' } : {}) }),
524
555
  ...(shot.png === undefined
525
556
  ? { image: 'none' }
526
557
  : {
@@ -582,7 +613,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
582
613
  ...(shot.pageState === 'ready-no-flows'
583
614
  ? { pageStateNote: 'Runtime çizildi ama hiç mount akışı koşmadı. Sayfa akışsızsa (giriş ekranı) ya da verisi veri kaynağı bağlantısından geliyorsa (connectData prop akış koşturmadan okunur; summaryText satırları gösterir) normaldir; veri bekliyor ve göremiyorsanız page_schema_get ile mount olayına bağlı akışa, bağlı prop için connect_data_get / connect_data_execute (op list) ile bağlantıya bakın.' }
584
615
  : shot.pageState === 'awaiting-user'
585
- ? { pageStateNote: "Bir akış kullanıcı penceresini bekliyor: önizlemenin biyometri penceresi açık (kök ADR 0021). Kare pencereyi gösterir; dalı sürmek için sonraki eylem { action: 'click', target: 'preview-biometric__verify' } (Doğrulandı say) ya da 'preview-biometric__cancel' (vazgeç). Production /view'da pencere yoktur." }
616
+ ? { pageStateNote: awaitingUserNote(shot.awaitingWindow) }
586
617
  : shot.pageState === 'ready-no-counter'
587
618
  ? { pageStateNote: 'Runtime koşan akış sayacı yayınlamıyor (eski runtime); hazırlık DOM durulmasından çıkarıldı, akışların bittiği kanıtlanamadı.' }
588
619
  : shot.pageState === 'error'
@@ -816,6 +847,9 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
816
847
  for (const entry of opRecords)
817
848
  reject('PAGE-008', entry, unknownEventOps([entry], typesByElementId, eventsBySlug));
818
849
  }
850
+ // g-21: tanınmayan prop platformda bütün yamayı `unknown-prop` ile düşürür; op'u adıyla burada söyleriz.
851
+ for (const issue of await unknownPropOps(platform, project, environment, applicationId, async () => (await readPage()).schema, allOps))
852
+ reject('unknown-prop', issue.op, [issue.message]);
819
853
  if (rejected.length > 0)
820
854
  throw new PlatformApiError(rejectedOpsMessage(rejected, batchIndexHint(opened)), 400);
821
855
  // TR-005 / TR-006: yamanın yükü op'ların EZDİĞİ kayıtlı değerle karşılaştırılır — orada zaten duran kanca eskidir.
@@ -102,7 +102,7 @@ export function registerPlatformTools(server, platform) {
102
102
  'Modülün iş kuralları rehberini döner. Bir modülde İLK yazma işleminden önce ilgili rehberi okuyun:',
103
103
  ...guides.map(name => `- ${name}${guideRouting[name] ? `: ${guideRouting[name]}` : ''}`),
104
104
  "Büyük rehberler (page ~120 KB) section'sız çağrıda TAM METİN DÖNMEZ: giriş + bölüm listesi (outline) döner, truncated: true olur. Gövdeyi section ile bölüm bölüm alın.",
105
- "Büyük gövdeyi (sayfa şeması, akış adımları, uç/workflow tanımı, patch op'ları) dosyaya yazıp bodyFile ile verin: gövde bağlamınızdan geçmez, satır içiyle aynı doğrulamadan geçer (page_*/flow_*/microservice_*/workflow_* create/update/patch). microservice_execute yanıt başlıkları varsayılan kısadır; ucun kendi başlığı için includeHeaders:'all'.",
105
+ "Büyük gövdeyi (sayfa şeması, akış adımları, uç/workflow tanımı, patch op'ları) dosyaya yazıp bodyFile ile verin: gövde bağlamınızdan geçmez, satır içiyle aynı doğrulamadan geçer (page_*/flow_*/microservice_*/workflow_* create/update/patch). Uzun kod alanı ayrı dosyada durabilir: gövdede { \"$file\": \"./adim.js\" } o dosyanın METNİYLE değişir (çalıştırılmaz; yol bodyFile'ın klasörüne göre, GW-013). microservice_execute yanıt başlıkları varsayılan kısadır; ucun kendi başlığı için includeHeaders:'all'.",
106
106
  "Örnek gövdeler ve kural katalogları (ngapps://examples/<ad>, ngapps://rules/<ad>) istemci MCP resource okuyamıyorsa da bu araçla okunur: platform_guide({ resource: 'ngapps://rules/connect-data' }) — guide verilmez; dizin için 'ngapps://examples/index'."
107
107
  ].join('\n');
108
108
  server.registerTool('platform_whoami', {
@@ -4,6 +4,7 @@ import { FUNCTIONS_NAMESPACE } from '../knowledge/functionGuards.js';
4
4
  import { reservedExpressionName } from '../knowledge/expressionPrelude.js';
5
5
  import { applyCatalogLayoutRules, applyCatalogLayoutRulesFrom, fetchCatalogComponents } from './components.js';
6
6
  import { PlatformApiError } from '../platform/errors.js';
7
+ import { closestNames } from '../platform/argumentAliases.js';
7
8
  const GUARD_CODES = {
8
9
  page: { events: 'PAGE-011', widgets: 'PAGE-012', noun: 'sayfa', tree: 'schema' },
9
10
  layout: { events: 'LAY-004', widgets: 'LAY-005', noun: 'layout', tree: 'views' },
@@ -102,7 +103,8 @@ export async function screenCatalogNotes(platform, project, environment, applica
102
103
  *
103
104
  * Neden yalnız bu yollarda: micro-op ucunda platformun kendi doğrulama duvarı zaten koşuyor
104
105
  * (`invalid-enum-value` · `unknown-prop` · `invalid-appearance`), tam şema PUT/POST'unda ise
105
- * koşmuyordu — aynı geçersiz değer bir yoldan 400, öbüründen 200 dönüyordu. Katalog okunamazsa
106
+ * koşmuyordu — aynı geçersiz değer bir yoldan 400, öbüründen 200 dönüyordu. Micro-op ucunda gateway
107
+ * yalnız tanınmayan prop'u önden adlandırır (`unknownPropOps`, g-21); enum ve görünüm kararı platformda kalır. Katalog okunamazsa
106
108
  * doğrulama susar ve yazma eskisi gibi geçer: bilinmeyen sözlük yüzünden geçerli bir gövdeyi
107
109
  * bloklamak, denetimi atlamaktan kötüdür.
108
110
  */
@@ -116,3 +118,107 @@ export async function assertScreenCatalog(platform, project, environment, applic
116
118
  const catalog = applyCatalogLayoutRulesFrom(components, nodes);
117
119
  return [...blockSizeNote(catalog.clamped), ...heightTypeNote(catalog.heights), ...sizeStringNote(catalog.sizes), ...droppableStatesNote(catalog.states)];
118
120
  }
121
+ /** Düğümün `props[prop]`'unu yazan op'lar (`PageSchemaMicroOpApplier.cs` `replace_prop` · `set_binding` · `set_prop_type`). */
122
+ const PROP_WRITING_OPS = new Set(['replace_prop', 'set_binding', 'set_prop_type']);
123
+ function registerInstances(nodes, parent, byId) {
124
+ return (Array.isArray(nodes) ? nodes : []).flatMap(node => {
125
+ if (!node || typeof node !== 'object')
126
+ return [];
127
+ const record = node;
128
+ const instance = { type: typeof record.type === 'string' ? record.type : undefined, parent, children: [], removed: false };
129
+ instance.children = registerInstances(record.children, instance, byId);
130
+ if (typeof record.elementId === 'string')
131
+ byId.set(record.elementId, instance);
132
+ return [instance];
133
+ });
134
+ }
135
+ function detach(instance) {
136
+ if (instance.parent)
137
+ instance.parent.children = instance.parent.children.filter(child => child !== instance);
138
+ }
139
+ function markRemoved(instance) {
140
+ instance.removed = true;
141
+ instance.children.forEach(markRemoved);
142
+ }
143
+ function collectSavedProps(nodes, into) {
144
+ for (const node of Array.isArray(nodes) ? nodes : []) {
145
+ if (!node || typeof node !== 'object')
146
+ continue;
147
+ const record = node;
148
+ if (typeof record.elementId === 'string' && record.props && typeof record.props === 'object') {
149
+ for (const prop of Object.keys(record.props))
150
+ into.add(`${record.elementId}/${prop}`);
151
+ }
152
+ collectSavedProps(record.children, into);
153
+ }
154
+ }
155
+ /**
156
+ * g-21 (D-21, plans/defter-saha-bulgulari) — micro-op yamasında tanınmayan prop'un ön denetimi. Platform
157
+ * `PageSchemaValidator.cs` `ValidateProp` aynı kararı `unknown-prop` ile verir ama BÜTÜN yamayı reddeder ve hangi op
158
+ * olduğunu ajan hata metninden ayıklar (Defter: `app-bar`'da `ariaLabel`). Burada op'un sırası, hedefi ve
159
+ * komponentin en yakın üç prop adı istek gitmeden söylenir; yama yine bütün reddedilir (op'u sessizce düşürmek
160
+ * yazanın beklediğini değiştirirdi). Sözlük `assertScreenCatalog`'unkiyle aynıdır (`GET /components/{app}`, yalnız
161
+ * üst düzey prop'lar) ve aynı kuralla susar: katalog okunamazsa, hedefin tipi bilinmiyorsa ya da tip katalogda
162
+ * yoksa op denetlenmez. Kayıtlı ağaç yalnız prop yazan op varsa okunur. `ops` batch zarfı açılmış listedir; indeks o
163
+ * listeye göredir.
164
+ *
165
+ * Platformdan GENİŞ reddetmemek için iki kural (g-21 incelemesi): (1) Kayıtlı düğüm prop'u zaten taşıyorsa karar
166
+ * platformundur — `ValidateProp` kayıtlı ağaçta aynı değerle duran tanımsız prop'u muaf tutar (`savedProps`) ve aynı
167
+ * yazım `noop`'tur (`PatchPageSchemaCommand.cs`). (2) Op'lar platformun uyguladığı SIRAYLA yürünür: hedefin tipi o
168
+ * andaki düğümünkidir (`add_component` ekler, `remove_component` alt ağacıyla siler, `move_component` taşır) ve aynı
169
+ * yamada sonra silinen düğüme yazan op denetlenmez — silinen düğüm doğrulanacak son ağaçta yoktur.
170
+ */
171
+ export async function unknownPropOps(platform, project, environment, applicationId, savedNodes, ops) {
172
+ if (!ops.some(entry => PROP_WRITING_OPS.has(String(entry.op)) && typeof entry.prop === 'string'))
173
+ return [];
174
+ const rules = catalogTypeRules(await fetchCatalogComponents(platform, project, environment, applicationId).catch(() => []));
175
+ if (rules.size === 0)
176
+ return [];
177
+ const nodes = await savedNodes().catch(() => []);
178
+ const byId = new Map();
179
+ const savedProps = new Set();
180
+ const writes = [];
181
+ registerInstances(nodes, undefined, byId);
182
+ collectSavedProps(nodes, savedProps);
183
+ ops.forEach((entry, index) => {
184
+ const target = typeof entry.target === 'string' ? entry.target : undefined;
185
+ if (entry.op === 'add_component') {
186
+ const parent = typeof entry.parent === 'string' ? byId.get(entry.parent) : undefined;
187
+ const added = registerInstances([entry.component], parent, byId);
188
+ parent?.children.push(...added);
189
+ }
190
+ else if (entry.op === 'remove_component' && target !== undefined) {
191
+ const instance = byId.get(target);
192
+ if (instance) {
193
+ detach(instance);
194
+ markRemoved(instance);
195
+ byId.delete(target);
196
+ }
197
+ }
198
+ else if (entry.op === 'move_component' && target !== undefined) {
199
+ const instance = byId.get(target);
200
+ const parent = typeof entry.parent === 'string' ? byId.get(entry.parent) : undefined;
201
+ if (instance) {
202
+ detach(instance);
203
+ instance.parent = parent;
204
+ parent?.children.push(instance);
205
+ }
206
+ }
207
+ else if (PROP_WRITING_OPS.has(String(entry.op)) && target !== undefined && typeof entry.prop === 'string') {
208
+ const instance = byId.get(target);
209
+ if (instance)
210
+ writes.push({ index, entry, target, prop: entry.prop, instance });
211
+ }
212
+ });
213
+ return writes.flatMap(({ index, entry, target, prop, instance }) => {
214
+ const type = instance.type;
215
+ const declared = type === undefined ? undefined : rules.get(type)?.props;
216
+ if (instance.removed || declared === undefined || declared.has(prop) || savedProps.has(`${target}/${prop}`))
217
+ return [];
218
+ const closest = closestNames(prop, [...declared.keys()], 3);
219
+ return [{
220
+ op: entry,
221
+ message: `ops[${index}] ${target}.${prop} — '${type}' komponentinin '${prop}' diye bir prop'u yok; en yakın: ${closest.join(' · ')}. Bütün liste component_get('${type}') ile okunur.`
222
+ }];
223
+ });
224
+ }
@@ -9,7 +9,7 @@ const variablesParam = z.array(z.object({
9
9
  key: z.string().min(1).describe("Değişken adı; sayfada state.page.<key> ile okunur"),
10
10
  value: z.unknown().describe('Herhangi bir JSON değeri; nesne verirseniz alanlarına state.page.<key>.<alan> ile erişilir'),
11
11
  storage: z.enum(['memory', ...STATE_STORAGE_KINDS]).optional()
12
- .describe("Değerin yaşadığı yer (yalnız mobil uygulamada uygulanır, MOB-014). Verilmezse kayıttaki tür korunur; yeni değişkende bellektir — state her sayfa açılışında buradaki value'dan başlar. 'device': cihaz deposu (AsyncStorage karşılığı), uygulama kapanıp açılınca son yazılan değer geri gelir. 'secure': güvenli depo (expo-secure-store), token gibi gizli ve küçük değerler için. 'memory': türü kaldırır. Çıkışta ikisi de silinir.")
12
+ .describe("Değerin yaşadığı yer (yalnız mobil uygulamada uygulanır, MOB-014). Verilmezse kayıttaki tür korunur; yeni değişkende bellektir — state.page her sayfa açılışında buradaki value'dan başlar; mobil kabukta state.app uygulama başına tek bellek deposudur (açılan sayfanın value'su yalnız depoda olmayan anahtarı doldurur, kök ADR 0031). 'device': cihaz deposu (AsyncStorage karşılığı), uygulama kapanıp açılınca son yazılan değer geri gelir. 'secure': güvenli depo (expo-secure-store), token gibi gizli ve küçük değerler için. 'memory': türü kaldırır. Çıkışta ikisi de silinir.")
13
13
  })).min(1).describe("Değişken listesi: [{ key: 'MyTest', value: { isActive: true } }]. Gateway bunu platformun beklediği JSON string'e çevirir.");
14
14
  const replaceParam = z.boolean().optional()
15
15
  .describe('true: seti komple bunlarla değiştir (diğer değişkenler silinir). Varsayılan false — gateway mevcut setle birleştirir.');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appaflytech/zeyrek-ai-mcp",
3
- "version": "0.0.52",
3
+ "version": "0.0.53",
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",
@@ -1,64 +0,0 @@
1
- # Mobil akış adımları rehberi
2
-
3
- Mobil uygulamada (`type:'mobile'`) akış motoru web'deki motorun aynısıdır (`platform_guide('page')`, `flow_create`).
4
- Bu rehber yalnız barındırıcıya — cihaza — dokunan adımların telefondaki sözleşmesini ve tariflerini anlatır.
5
- Ekranın kendisi, sayfa olayları (`mount` · `focus` · `destroy`) ve uzak push `platform_guide('mobile')`'dadır.
6
-
7
- ## Adımlar
8
-
9
- Motorun kendi adımları (`setter`, `condition`, `microservice`, `code`, `function`, `flow-trigger`,
10
- `validation`) iki platformda aynıdır. Barındırıcıya dokunan adımlar:
11
-
12
- | Adım | Mobilde |
13
- |---|---|
14
- | `redirect` | yığına iter (göreli yol) ya da dışarıda açar (tam adres) |
15
- | `navigate` | eski mobil şemaların adı — `redirect`'in takma adıdır; gateway `redirect`'e çevirir |
16
- | `alert` | tipli uyarı (`success`/`error`/`info`/`warning`) toast'tur ve beklemez; düz uyarı onay bekler. `message`/`description` metin şablonudur (`"{{ t('kayit.tamam') }}"`; `platform_guide('page')` §alert). Eylem düğmeli bildirim ("Silindi · Geri al") `alert`le kurulmaz: sayfaya bir `toast` bileşeni koyun (`actions: [{ text, flow }]`, gerekirse `keepOpen`), akış setter'la `isOpen: true` yazar; basış eylemin kendi akışını koşar (`actionClick { text, index }`) |
17
- | `confirm` | `yes` / `no` dalları; `message`/`title`/`confirmText`/`cancelText` metin şablonudur (`{{ }}`, çeviri `t('anahtar')`) |
18
- | `download` | adres cihazın açma sayfasında açılır; ikili gövde (JSON değil) önbelleğe dosya olarak yazılıp paylaşım sayfasında (`expo-sharing`) açılır, Studio mobil önizlemesi dosyayı indirir (`MOB-005`, bilgi) |
19
- | `storage` | `args.name` `state.page.<key>` / `state.app.<key>` ise o state'i yazar (`platform_guide('mobile')` §Kalıcı state), değilse cihaz deposuna |
20
- | `setcookie` | güvenli cihaz deposuna (SecureStore) yazar; değer sonraki her istekte `X-Flow-Cookie` başlığıyla gider ve middleware/workflow onu web'deki gibi `cookie.<ad>` olarak okur — `Cookie` başlığıyla gitmez, çünkü iOS onu yerel çerez kavanozuyla virgülle birleştirip bozuyordu (`MOB-005`, bilgi); aynı adlı sunucu çerezi varsa o kazanır. Studio paleti mobilde de sunar |
21
- | `share` | RN paylaşım sayfası (metin + bağlantı; Android'de bağlantı metne eklenir, `title` iOS'ta e-posta konusu, Android'de seçici başlığı); akış `always` ile sürer — iOS'ta sayfa kapanınca, Android'de seçici açılır açılmaz (RN kapanışı bildirmez). Argümanlar `platform_guide('page')` §`share` adımı |
22
- | `haptic` | dokunsal geri bildirim (`expo-haptics`, izin istemez). `args.value` stil, **düz metin**: `success` · `warning` · `error` · `light` · `medium` · `heavy` · `selection`; boş → `medium`. Değer yazmaz, hep `always`. Web'de titreşim varsa kısa titreşim, yoksa hiçbir şey (paletten yalnız mobilde sunulur) |
23
- | `location` | cihaz konumu (`expo-location`; ilk kullanımda izin sorulur; derleme konumu taşımıyorsa sorulmaz, `error` dalı `status: 'unavailable'`). `args.value` doğruluk, düz metin: `low` · `balanced` · `high` (boş → `balanced`); `args.isRaw: true` → adres çözülmez. Sonuç `Steps.<adım>.data = { latitude, longitude, accuracy, address }`, `address = { country, region, subregion, city, district, street, postalCode }` ya da `null` (Türkiye'de il `region`, ilçe `subregion`, yoksa `city`). İzin reddi `Steps.<adım>.error = { status: 'denied', message }`, okunamayan konum `unavailable`: adımın `error` dalı bağlıysa o koşar (`always` koşmaz), yoksa akışın hata akışı, o da yoksa `always` (`data: null`). Web'de tarayıcı konumu, adres hep `null` |
24
- | `notification-permission` | bildirim izni ister (sorulmuşsa yeniden sormaz, kalıcı red yeniden sorulmaz). Sonuç `Steps.<adım>.data.status`: `granted` · `denied` · `undetermined`; red hata değildir, hep `always` |
25
- | `notification-schedule` | yerel bildirim planlar (`expo-notifications`; uzak push değil). `args.name` kimlik ve `args.title` başlık **metin şablonudur** — yalnız `{{ }}` içi çözülür (`'ilac-{{ item.id }}-0800'` kayıt başına ayrı kimlik, `"{{ t('ilac.vakti') }}"` çeviri; `{{ }}` dışındaki ifade çözülmez, FLOW-022); aynı kimlik eskisinin yerini alır. `args.message` gövde (**ifade**), `args.value` zaman (**ifade**): sonucu `'HH:mm'` → her gün, `{ weekday: 1–7, time: 'HH:mm' }` → her hafta (1 = Pazartesi), ISO tarih-saat → bir kez. `args.path` dokununca açılacak sayfanın slug'ı, düz metin — şablon çözmez (+ `args.query` ifade), boşsa uygulama açılır. İzin yoksa `error.status: 'denied'`, geçmiş ya da okunamayan zaman `invalid`: `error` dalı → hata akışı → `always`. Web'de desteklenmez (`unsupported`) — sayfa kapalıyken tarayıcı gösteremez |
26
- | `notification-cancel` | `args.name` kimlikli planlı bildirimi siler (planlamadaki gibi metin şablonu: `'ilac-{{ item.id }}-0800'`), `'*'` hepsini; hep `always` |
27
- | `permission` | bir cihaz iznine bakar ya da ister. `args.value` izin türü, **düz metin — ifade olamaz** (mağaza derlemesinin taşıyacağı izinler bu değerden, akış koşmadan hesaplanır; `FLOW-038`); `args.name` kip: `check` yalnız okur (sistem penceresi açılmaz), `request` verilmemiş ve hâlâ sorulabilen izni ister (boş → `request`). Sonuç `Steps.<adım>.data = { status, canAskAgain }`: `granted` · `denied` · `undetermined` · `unavailable` (derleme bu izni taşımıyor ya da web önizlemesi); `canAskAgain: false` → yalnız Ayarlar'dan değişir. `tracking` (iOS App Tracking Transparency) sonucu ayrıca `advertisingId` taşır: izin `granted` iken IDFA (iOS) / AAID (Android), değilse `null`; oynatıcı `tracking`'i hiç taşımaz (`unavailable`). Red hata değildir, hep `always`. Türler: `mobile_store_get { detail: ['permissions'] }` (`support: supported` satırlarının `key`'i) |
28
- | `open-settings` | uygulamanın sistem ayar sayfasını açar; `Steps.<adım>.data.opened`. Argüman almaz |
29
- | `back` | gezinme yığınında bir geri (iOS geri kaydırmasıyla aynı sonuç; kök ADR 0013). Argüman almaz (yazılan okunmaz), sonuç yazmaz, hata üretmez, hep `always`. Yığının ilk ekranında hiçbir şey yapmaz; Studio önizlemesinde runtime'ın kendi gezinmesi yoksa yine hiçbir şey, web uygulamasında tarayıcının geri'sidir. Formu kaydedip önceki ekrana dönmek için `redirect` yerine bunu kullanın: `redirect` yığına yeni ekran iter, geri kaydırması kaydedilen forma döner. 2026-10-08 öncesi runtime ve backend `back`'i tanımaz |
30
- | `ad-show` | tam ekran reklam (kök ADR 0012). `args.value` mağaza ayarındaki yerleşimin **adı**, düz metin (`FLOW-039`); biçimi `interstitial` · `rewarded` · `rewardedInterstitial` olmalı. Reklam yüklenip gösterilir, kullanıcı kapatınca `Steps.<adım>.data = { shown: true, reward }` ile sürer — `reward` ödüllü reklamı sonuna kadar izleyende `{ type, amount }`, değilse `null` (ödül ayrı dal değil, `condition` ile okunur). 10 sn'de reklam yoksa, SDK hata verirse, derleme reklam taşımıyorsa ya da rıza yoksa `error.status: 'unavailable'`; tanımsız ya da banner yerleşimi `invalid`: `error` dalı → hata akışı → `always`. Oynatıcı ve web önizlemesi her zaman `unavailable` — reklamsız dalı orada sınayın. Paletten yalnız mobilde |
31
-
32
- ## Bildirim ve izin
33
-
34
- Bildirim adımları paletten yalnız mobil uygulamada sunulur. Tarif — S04 "her gün hatırlat": önce
35
- `notification-permission` (S03 düğmesi), sonra `timepicker`'ın değeriyle `notification-schedule`
36
- (`value: "<saat>.value"`, değer `HH:mm`) ve `error` dalında kullanıcıya mesaj. Çevrimdışı bandı adım değil
37
- ifadedir: `isHidden: {{ Device.isOnline }}` (`platform_guide('expressions')`).
38
-
39
- **İzin tarifi** (izin, ihtiyaç anında — düğmeye basılınca — istenir; sayfa açılışında değil): `permission`
40
- `check` → `condition`, **bu sırayla**: `status === 'granted'` ise işe devam; `'unavailable'` ise özelliği sunma
41
- (Ayarlar'da açılacak izin yoktur — `canAskAgain` burada da `false` gelir); `canAskAgain` ise `permission` `request`
42
- ve sonucuna göre devam; değilse nedenini söyleyip `open-settings`. `location` adımı, `map.showUser` ve dosya alanının
43
- kamera/galeri kaynağı izni kendileri de ister — `permission` adımı *ne zaman* sorulacağını seçmek ve sonucu
44
- okumak içindir. `unavailable` bir hata değil, derlemenin o izni taşımadığının haberidir
45
- (`platform_guide('mobile-publish')` §İzinler).
46
-
47
- ## Reklam
48
-
49
- **Reklam tarifi** (kök ADR 0012): (1) `mobile_store_update { settings: { ads: { enabled: true, iosAppId, androidAppId,
50
- testMode: true, units: [{ name, format, ios, android }] } } }` — reklam, App ID'si olan platformun derlemesine `tracking`'i de sokar (ATT metni
51
- gider, kip `off` olamaz); (2) banner için sayfaya `adBanner` `unit: '<banner yerleşimi>'`; (3) geçiş/ödüllü reklam için
52
- düğme akışına `ad-show` `value: '<yerleşim>'`, `error` dalında reklamsız devam; (4) ödül `condition`:
53
- `Steps.<adım>.data?.reward` doluysa ödülü ver. Açılış reklamı adım değil ayardır (`ads.appOpenUnit`). Sıra cihazda:
54
- rıza formu → açılış izinleri (ATT) → SDK; iOS'ta izleme izni yoksa reklam kişiselleştirilmeden istenir. Yerleşim adının
55
- ayarda tanımlı olduğu sayfa/akış yazımında denetlenemez — `mobile_store_get` `settings.ads.units` ve `adsWarnings`
56
- (`MOB-026`); yayın ayrıntısı `platform_guide('mobile-publish')` §Reklamlar.
57
-
58
- ## Konum ve harita
59
-
60
- `style`, `accuracy`, `reverseGeocode` gibi anahtarlar sözleşmede yoktur ve platform onları sessizce düşürür
61
- (`FLOW-029`). "Haritada aç" için adım yoktur: `map`'in `press` olayı düğüme `mapsUrl` yazar, akış
62
- `redirect` `isRaw: true`, `path: '<harita>.mapsUrl'` ile açar. Tarif — L05 "mevcut konumumu kullan":
63
- düğmenin `click`'i → `location` (`always` → setter `il.value = Steps.konum.data.address?.region`, `error` →
64
- setter hata metni). Uzak push (sunucudan gelen bildirim): `platform_guide('mobile')` §Uzak push.