@appaflytech/zeyrek-ai-mcp 0.0.49 → 0.0.50
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.
- package/dist/knowledge/componentEvents.js +1 -1
- package/dist/knowledge/mobileComponentCatalog.js +6 -3
- package/dist/knowledge/mobileComponentEvents.js +9 -0
- package/dist/knowledge/mobileStore.js +14 -0
- package/dist/knowledge/platform/lint.js +36 -3
- package/dist/knowledge/platform/mobileRules.js +6 -0
- package/dist/platform/capture/actions.js +67 -0
- package/dist/platform/capture/capture.js +26 -21
- package/dist/platform/storeArt/compose.js +114 -0
- package/dist/platform/storeArt/deck.js +133 -0
- package/dist/platform/storeArt/gallery.js +331 -0
- package/dist/platform/storeArt/layouts.js +121 -0
- package/dist/platform/storeArt/styles.js +54 -0
- package/dist/resources/examples/mobile-reorder-persist.json +491 -0
- package/dist/resources/guides/microservice.md +7 -0
- package/dist/resources/guides/mobile-publish.md +13 -0
- package/dist/resources/guides/mobile.md +4 -0
- package/dist/resources/guides/page.md +62 -0
- package/dist/resources/prompts/mobile-publish-prepare.md +3 -1
- package/dist/resources/store-art/editor.css +389 -0
- package/dist/resources/store-art/editor.html +123 -0
- package/dist/resources/store-art/editor.js +990 -0
- package/dist/tools/mobileStore.js +56 -19
- package/dist/tools/pages.js +21 -5
- package/package.json +1 -1
|
@@ -5,9 +5,14 @@ import { destructive, environmentParam, guid, projectParam, readOnly, updates, w
|
|
|
5
5
|
import { unwrap } from './screens.js';
|
|
6
6
|
import { PlatformApiError } from '../platform/errors.js';
|
|
7
7
|
import { handle } from '../platform/responses.js';
|
|
8
|
+
import { launchScreenshotBrowser } from '../platform/capture/capture.js';
|
|
9
|
+
import { previewStoreArt, sharedGalleryServer } from '../platform/storeArt/gallery.js';
|
|
10
|
+
import { EYEBROW_MAX, MAX_STYLES, SUBTITLE_MAX, TITLE_MAX } from '../platform/storeArt/deck.js';
|
|
11
|
+
import { LAYOUT_IDS } from '../platform/storeArt/layouts.js';
|
|
12
|
+
import { STYLE_IDS } from '../platform/storeArt/styles.js';
|
|
8
13
|
import { attachFindings, enforcePolicy, evaluatePolicy } from '../knowledge/policy.js';
|
|
9
14
|
import { policyRules } from '../knowledge/rules.js';
|
|
10
|
-
import { APPLE_RELEASE_TYPES, ASSET_KINDS, BUILD_ASSET_KINDS, DECLARATION_KEYS, DEVICE_CLASSES, MAX_ASSET_BYTES, ORIENTATIONS, PLAY_TRACKS, RELEASE_STATUSES, SCREENSHOT_CAPTURE_DEVICES, SPLASH_RESIZE_MODES, USER_INTERFACE_STYLES, adsStoreWarnings, trackingDeclarationWarnings, assetSlotProblem, changedStoreFields, mergeStoreSettings, permissionLaunchShellWarnings, permissionLockShellWarnings, permissionStoreBuildWarnings } from '../knowledge/mobileStore.js';
|
|
15
|
+
import { APPLE_RELEASE_TYPES, ASSET_KINDS, BUILD_ASSET_KINDS, COLOR_PATTERN, DECLARATION_KEYS, DEVICE_CLASSES, LOCALE_PATTERN, MAX_ASSET_BYTES, MAX_IMAGES_PER_SLOT, ORIENTATIONS, PLAY_TRACKS, RELEASE_STATUSES, SCREENSHOT_CAPTURE_DEVICES, SPLASH_RESIZE_MODES, USER_INTERFACE_STYLES, adsStoreWarnings, trackingDeclarationWarnings, assetSlotProblem, changedStoreFields, mergeStoreSettings, permissionLaunchShellWarnings, permissionLockShellWarnings, permissionStoreBuildWarnings } from '../knowledge/mobileStore.js';
|
|
11
16
|
/**
|
|
12
17
|
* Mobil mağaza yayınının ajan yolu (`plans/mobil-magaza-yayini` g-11, `plans/mobil-yayin-mcp` g-01):
|
|
13
18
|
* gizli olmayan mağaza ayarı, mağaza görselleri, beyanlar ve derleme işi. Gizli bilgi (Apple/Google
|
|
@@ -290,6 +295,27 @@ async function readAsset(kind, path, content, filename) {
|
|
|
290
295
|
const extension = png ? 'png' : 'jpg';
|
|
291
296
|
return { bytes, name: filename ?? (path !== undefined ? basename(path) : `${kind}.${extension}`), contentType: png ? 'image/png' : 'image/jpeg' };
|
|
292
297
|
}
|
|
298
|
+
/**
|
|
299
|
+
* `mobile_asset_create`'in yükleme yolu; galeri sayfasının "Seçilenleri yükle"si de bunu çağırır (ADR 0004),
|
|
300
|
+
* böylece yuva kuralı ve biçim denetimi iki yolda aynıdır.
|
|
301
|
+
*/
|
|
302
|
+
async function uploadStoreAsset(platform, appId, { kind, path, content, filename, locale, deviceClass }) {
|
|
303
|
+
// Backend boş dili null sayar (`MobileStoreService.UploadAssetAsync`); data URL öneki base64 değildir.
|
|
304
|
+
const slotLocale = locale?.trim() ? locale.trim() : undefined;
|
|
305
|
+
const problem = assetSlotProblem(kind, slotLocale, deviceClass);
|
|
306
|
+
if (problem)
|
|
307
|
+
throw new PlatformApiError(`mobile_asset_create: ${problem}. Hiçbir şey yüklenmedi.`, 400);
|
|
308
|
+
const file = await readAsset(kind, path, content?.replace(/^data:[^,]*;base64,/, ''), filename);
|
|
309
|
+
const asset = unwrap(await platform.org('POST', `/mobile-store/${appId}/assets`, {
|
|
310
|
+
multipart: [
|
|
311
|
+
{ name: 'file', filename: file.name, contentType: file.contentType, content: file.bytes },
|
|
312
|
+
{ name: 'kind', value: kind },
|
|
313
|
+
...(slotLocale === undefined ? [] : [{ name: 'locale', value: slotLocale }]),
|
|
314
|
+
...(deviceClass === undefined ? [] : [{ name: 'deviceClass', value: deviceClass }])
|
|
315
|
+
]
|
|
316
|
+
}));
|
|
317
|
+
return { ...assetSummary(asset), ...(kind === 'screenshot' || kind === 'promo' ? {} : { note: `'${kind}' tekli yuvadır: varsa önceki görsel silindi.` }) };
|
|
318
|
+
}
|
|
293
319
|
const captureTable = DEVICE_CLASSES.map(deviceClass => {
|
|
294
320
|
const device = SCREENSHOT_CAPTURE_DEVICES[deviceClass];
|
|
295
321
|
return `${deviceClass} → device { width: ${device.width}, height: ${device.height}, scale: ${device.scale} } (${device.width * device.scale}×${device.height * device.scale})`;
|
|
@@ -356,8 +382,10 @@ function buildSummary(job, now) {
|
|
|
356
382
|
...(isObject(job.error) ? { next: `Hata kodu → öneri: platform_guide('mobile-publish', 'hata-kodlari'); ayrıntı mobile_build_log` } : {})
|
|
357
383
|
};
|
|
358
384
|
}
|
|
359
|
-
export function registerMobileStoreTools(server, platform) {
|
|
385
|
+
export function registerMobileStoreTools(server, platform, storeArt = {}) {
|
|
360
386
|
const config = platform.config;
|
|
387
|
+
const launchStoreArtBrowser = storeArt.launcher ?? (() => launchScreenshotBrowser(config));
|
|
388
|
+
const galleryServer = storeArt.gallery ?? sharedGalleryServer;
|
|
361
389
|
server.registerTool('mobile_store_get', {
|
|
362
390
|
title: 'Mobil mağaza ayarını oku',
|
|
363
391
|
description: "Mobil uygulamanın gizli olmayan mağaza ayarını döner (GET mobile-store/{appId}): kimlik (bundleIdentifier, androidPackage, scheme), sürüm ve sayaçlar, görünüm, izin metinleri, kipleri ve açılışta sorulanlar (permissions, permissionModes, launchPermissions), listing (dil başına mağaza metni), urls, apple, play, review; lockedFields (ilk derlemeden sonra değişmeyen alanlar), görsel yuvası başına sayı (assets: 'screenshot/tr/iphone69': 3) ve beyanlar (declarations: anahtar → işaretli mi); settings.ads reklam ayarıdır (kök ADR 0012: enabled, App ID'ler, testMode, consentForm, appOpenUnit, units[] = adBanner.unit ve ad-show.value'nun andığı yerleşimler) ve reklam açıkken adsWarnings döner (MOB-026: test kipi, platformda birimsiz yerleşim, konsol beyanları); settings.appLock uygulama kilididir (kök ADR 0021: enabled, timeoutSeconds 0·60·300·900). Kayıt hiç yazılmamışsa persisted:false ve VARSAYILANLAR döner (kaydedilmez). manifest:true derlemeye gidecek manifesti de ekler (manifest-preview; sayaçlar SIRADAKİ numara); üretilemiyorsa manifestError alan adıyla döner. detail ayrıntı bölümleri ekler: 'assets' görselleri id'li liste olarak döner (mobile_asset_update / mobile_asset_delete id ister), 'signing' uygulamanın kimlik bilgilerini (signing.app: iOS profili, Android keystore, harita anahtarı, demo hesabı) ve organizasyonun Apple/Google hesaplarını + sertifikalarını (signing.accounts), 'runners' çevrimiçi runner'ları, 'audit' son 20 gizli bilgi olayını (subject: hangi tür, kim, ne zaman, hangi iş), 'permissions' izin görünümünü (permissions.permissions[]: uygulamanın isteyebileceği her izin için mode, text/defaultText, usage — izni kullanan sayfa·bileşen ya da akış adımı (setter ile yazılan tetikleyici prop dahil), ilk 20 yer + usageCount, çağrının environment'ından; usageReadable false ise o ortam okunamamıştır ve boş usage 'kullanılmıyor' demek değildir —, build — mağaza derlemesi taşıyor mu ve neden; HER ZAMAN production'dan: build.usageCount production'daki kullanım, build.staticUsageCount onun yazılı (ifadesiz) kısmı —, devices — aktif cihazların izin durumu sayıları; missingFromStoreBuilds — o izni taşımayan son mağaza derlemeleri; shell.carriesPermissions false ise kipler ve kabuğun henüz taşımadığı izinler bir sonraki kabuk sürümüyle etkili olur; shell.carriesLaunchPermissions false ise açılışta sorulan izinler (askOnLaunch) o kabukta sorulmaz, shell.carriesAppLock false ise açık uygulama kilidi o kabukta yoktur — permissionWarnings söyler), 'status' mağaza durumunu (GET store-status: apple { state, appStoreState, versionString, releaseType, phased* }, play.tracks[] { track, status, versionCodes, rolloutPercent }, actions[] { platform, target, track, irreversible, consequence } — Studio › Yayınla'daki yayın düğmeleri; irreversible:true geri alınamaz, eylemi MCP başlatmaz, kullanıcıya consequence'ı söyleyip Studio'ya yönlendirin), 'changes' son production dağıtımından beri değişenleri iki kovada (GET changes-since-production: live — dağıtımla telefonlara gider, mağaza gerekmez; store — izin/kimlik/görsel gibi yeni mağaza sürümü isteyenler). Gizli DEĞER hiçbir bölümde yoktur — yalnız tür, maskeli id ve meta (ekip, son kullanma); bir bölüm okunamazsa (uç hatası) <bölüm>Error döner, çağrı düşmez. Gizli bilgiyi EKLEMEK/DEĞİŞTİRMEK MCP'den yapılmaz — Studio › Yayın › Hesaplar.",
|
|
@@ -575,23 +603,32 @@ export function registerMobileStoreTools(server, platform) {
|
|
|
575
603
|
// Tekli yuvada önceki görsel ve dosyası silinir (`MobileStoreService.UploadAssetAsync`): geri alınamaz.
|
|
576
604
|
// Ekran görüntüsü eklendiği için idempotent değildir.
|
|
577
605
|
annotations: { ...writes, destructiveHint: true }
|
|
578
|
-
}, handle(config,
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
606
|
+
}, handle(config, ({ applicationId: appId, ...upload }) => uploadStoreAsset(platform, appId, upload)));
|
|
607
|
+
const hexColor = z.string().regex(COLOR_PATTERN, "Renk '#RRGGBB' biçiminde verilir");
|
|
608
|
+
server.registerTool('mobile_asset_preview', {
|
|
609
|
+
title: 'Mağaza pazarlama görseli çiz',
|
|
610
|
+
description: "Ham ekran görüntülerini mağaza pazarlama görseline çizer ve aynı kareleri en çok dört hazır stilde yan yana üretir (çeşitleme). Stiller (styles): clean (açık düz), midnight (koyu gradyan + parıltı), aurora (renkli ışık lekeleri), spotlight (koyu + merkez ışık), swiss (ızgara, sola hizalı büyük harf), japandi (kırık beyaz + serif), pastel (yumuşak gradyan + yuvarlak yazı), neon (siyah nokta deseni + neon vurgu). Kare başına yerleşim (slides[i].layout): classic (cihaz ortada), bleed (büyük cihaz alttan taşar, başlık üstte), tilt (3D eğik cihaz), text (cihazsız büyük başlık; path istemez). Çerçeve CSS'tir (Apple görseli değil); çıktı deviceClass'ın mağaza ölçüsünde alfasız JPEG'dir (iphone69 1320×2868, ipad13 2064×2752, phone 1080×2160, tablet7 1200×1920, tablet10 1600×2560), dosya adı <stil>-NN.jpg; kartlar kare sırasıyla (her karenin stilleri yan yana) dizilir ve bu sıra yükleme sırasıdır. Girdi: her kare için page_screenshot { environment: 'production', device, format: 'jpeg', language: <locale>, colorScheme: 'light', outPath } çıktısının yolu; araç sayfa ÇEKMEZ. Platforma kendisi hiçbir şey YAZMAZ: dosyalar yanıttaki directory'ye yazılır ve yanıt galleryUrl taşır (http://127.0.0.1:<port>/g/<anahtar>/) — adresi KULLANICIYA verin; sayfa bir EDİTÖRDÜR: kullanıcı başlığı karenin üstünde yazarak değiştirir, stil/yerleşim/renk/ham kare seçer, kartı kopyalar, sırayı sürükleyerek değiştirir, kartı yeniden çizer ve seçtiklerini 'Seçilenleri yükle' ile screenshot/<locale>/<deviceClass> yuvasına deste sırasıyla ekler (kendiniz ayrıca yüklemeyin, yuvaya kopya düşer). Kullanıcının düzenlemeleri directory/deck.json'dadır. Galeri gateway süreci yaşadıkça açıktır; AUTH_MODE=passthrough'ta galeri açılmaz ve yükleme mobile_asset_create { path: files[i].path, kind: 'screenshot', deviceClass, locale } ile yapılır. Tuzaklar: kare deviceClass'ın cihaz ölçüsüyle çekilmediyse ekrana kırpılarak oturur ve warnings söyler; başlık en çok iki satır (text'te beş), alt başlık iki satır çizilir, taşan metin kesilir; başlık uygulamanın gerçekten yaptığını söyler (App Review 2.3.7: fiyat, sıralama, rakip adı yok) ve kare o yuvanın dilinde çekilmiş olmalıdır; yazı tipleri işletim sisteminindir. Uygulamanın rengi palette ile stillere uygulanır (theme_get).",
|
|
611
|
+
inputSchema: {
|
|
612
|
+
applicationId,
|
|
613
|
+
deviceClass: z.enum(DEVICE_CLASSES).describe("Yuvanın cihaz sınıfı: iphone69 (iPhone 6.9\"), ipad13 (iPad 13\"), phone, tablet7, tablet10 (Play). Ham kareler page_screenshot'a bu sınıfın device'ıyla çekilir"),
|
|
614
|
+
locale: z.string().regex(LOCALE_PATTERN).describe("Yuvanın dili ('tr', 'en-US'); başlıklar bu dilde yazılır, ham kareler page_screenshot { language } ile bu dilde çekilir"),
|
|
615
|
+
slides: z.array(z.object({
|
|
616
|
+
path: z.string().min(1).optional().describe("Ham karenin yerel yolu (page_screenshot outPath); layout 'text' dışında zorunlu"),
|
|
617
|
+
eyebrow: z.string().trim().min(1).max(EYEBROW_MAX).optional().describe('Başlığın üstündeki küçük büyük harf etiket (ör. "Planla")'),
|
|
618
|
+
title: z.string().trim().min(1).max(TITLE_MAX).describe("Başlık; en çok iki satır ('text' yerleşiminde beş) çizilir"),
|
|
619
|
+
subtitle: z.string().trim().min(1).max(SUBTITLE_MAX).optional().describe('Alt başlık; en çok iki satır çizilir'),
|
|
620
|
+
layout: z.enum(LAYOUT_IDS).optional().describe("Yerleşim; varsayılan 'classic'. Ardışık karelerde yerleşimi değiştirmek seriyi canlı gösterir")
|
|
621
|
+
})).min(1).max(MAX_IMAGES_PER_SLOT).describe(`Kareler mağazadaki sırayla; yuva en çok ${MAX_IMAGES_PER_SLOT} görsel alır`),
|
|
622
|
+
styles: z.array(z.enum(STYLE_IDS)).min(1).max(MAX_STYLES).optional().describe("Çizilecek stiller (tekrarsız, 1–4); her kare her stilde çizilir ve galeride stil başına bir satır olur. Varsayılan ['clean']"),
|
|
623
|
+
palette: z.object({
|
|
624
|
+
primary: hexColor.optional().describe('Stillerin vurgu rengi (üst etiket, parıltı, aurora lekeleri); uygulamanın ana rengi'),
|
|
625
|
+
text: hexColor.optional().describe('Başlık rengi; stilin zeminiyle yeterli karşıtlıkta seçin')
|
|
626
|
+
}).optional(),
|
|
627
|
+
captionPosition: z.enum(['top', 'bottom']).optional().describe("classic ve tilt'te başlığın yeri; varsayılan 'top' (bleed hep üst, text ortada)"),
|
|
628
|
+
frame: z.boolean().optional().describe('false: çerçevesiz, köşeleri yuvarlatılmış gölgeli ekran; varsayılan true')
|
|
629
|
+
},
|
|
630
|
+
annotations: readOnly
|
|
631
|
+
}, handle(config, ({ applicationId: appId, ...input }) => previewStoreArt(launchStoreArtBrowser, config, galleryServer, input, file => uploadStoreAsset(platform, appId, { kind: 'screenshot', path: file, locale: input.locale, deviceClass: input.deviceClass }))));
|
|
595
632
|
server.registerTool('mobile_asset_update', {
|
|
596
633
|
title: 'Mağaza görsellerini sırala',
|
|
597
634
|
description: "Bir yuvadaki görsellerin sırasını yazar (PUT mobile-store/{appId}/assets/order): dizideki sıra mağazadaki gösterim sırası olur (ilk ekran görüntüsü mağazada önce görünür). assetIds TEK yuvanın (ör. 'screenshot/tr/iphone69') BÜTÜN görsellerini içerir — backend her id'ye dizideki indeksini yazar ve yuvayı denetlemez; eksik liste ya da iki yuvanın karışımı sessizce çakışan sıra üretirdi, bu yüzden gateway istek gitmeden kaydı okur ve bunları durdurur. Id'ler: mobile_store_get { detail: ['assets'] }.",
|
package/dist/tools/pages.js
CHANGED
|
@@ -300,12 +300,14 @@ 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', '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'); 'tab' için sekme GRUBUNUN kendisi ('tabs_settings'), 'select' için açılır kutu, 'submit' için form komponenti"),
|
|
305
|
-
to: z.string().min(1).optional().describe("
|
|
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"),
|
|
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
|
-
ms: z.number().int().min(0).max(15000).optional().describe("'wait' için bekleme süresi")
|
|
308
|
-
|
|
307
|
+
ms: z.number().int().min(0).max(15000).optional().describe("'wait' için bekleme süresi"),
|
|
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
311
|
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)."),
|
|
310
312
|
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."),
|
|
311
313
|
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."),
|
|
@@ -337,6 +339,20 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
337
339
|
if (incompleteDrag >= 0) {
|
|
338
340
|
throw new PlatformApiError(`page_screenshot: actions[${incompleteDrag}] 'drag' adımı hem target (sürüklenen öğe) hem to (bırakma hedefi) ister — ör. {action:'drag', target:'kb_board__card-42', to:'kb_board__col-Assigned'}.`);
|
|
339
341
|
}
|
|
342
|
+
const misplacedOffset = actions?.findIndex(step => step.action !== 'swipe' && (step.dx !== undefined || step.dy !== undefined)) ?? -1;
|
|
343
|
+
if (misplacedOffset >= 0) {
|
|
344
|
+
throw new PlatformApiError(`page_screenshot: actions[${misplacedOffset}] '${actions?.[misplacedOffset]?.action}' adımı dx/dy almaz — kayma yalnız 'swipe' jestinindir.`);
|
|
345
|
+
}
|
|
346
|
+
const swipeIndex = actions?.findIndex(step => step.action === 'swipe' && (step.target === undefined || (step.to === undefined) === (step.dx === undefined && step.dy === undefined))) ?? -1;
|
|
347
|
+
if (swipeIndex >= 0) {
|
|
348
|
+
throw new PlatformApiError(actions?.[swipeIndex]?.target === undefined
|
|
349
|
+
? `page_screenshot: actions[${swipeIndex}] 'swipe' adımı target (jestin basıldığı öğe) ister — ör. {action:'swipe', target:'lst__row-2__handle', to:'lst__row-0'}.`
|
|
350
|
+
: `page_screenshot: actions[${swipeIndex}] 'swipe' adımı ya to ya dx/dy ister (bitiş öğesi ya da göreli kayma), ikisini birden ya da hiçbirini değil — ör. {action:'swipe', target:'lst__row-1', dx:-160, dy:0}.`);
|
|
351
|
+
}
|
|
352
|
+
const stillSwipe = actions?.findIndex(step => step.action === 'swipe' && step.to === undefined && (step.dx ?? 0) === 0 && (step.dy ?? 0) === 0) ?? -1;
|
|
353
|
+
if (stillSwipe >= 0) {
|
|
354
|
+
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
|
+
}
|
|
340
356
|
if (!config.studioBaseUrl) {
|
|
341
357
|
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).");
|
|
342
358
|
}
|
package/package.json
CHANGED