@appaflytech/zeyrek-ai-mcp 0.0.21 → 0.0.22

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 (51) hide show
  1. package/dist/config.js +2 -0
  2. package/dist/knowledge/componentCatalog.js +3 -3
  3. package/dist/knowledge/componentEvents.js +9 -2
  4. package/dist/knowledge/mobileComponentCatalog.js +543 -0
  5. package/dist/knowledge/mobileComponentEvents.js +98 -0
  6. package/dist/knowledge/normalize.js +52 -11
  7. package/dist/knowledge/platform/lint.js +205 -0
  8. package/dist/knowledge/platform/mobile.js +78 -0
  9. package/dist/knowledge/platform/mobileRules.js +123 -0
  10. package/dist/knowledge/platform/mobileTabs.js +108 -0
  11. package/dist/knowledge/platform/profile.js +20 -0
  12. package/dist/knowledge/platform/resolve.js +122 -0
  13. package/dist/knowledge/platform/web.js +38 -0
  14. package/dist/knowledge/rules.js +6 -8
  15. package/dist/knowledge/styleResolve.js +1 -1
  16. package/dist/knowledge/styleResolveNative.js +410 -0
  17. package/dist/knowledge/themeResolve.js +3 -3
  18. package/dist/platform/capture/actions.js +333 -0
  19. package/dist/platform/capture/browser.js +1 -0
  20. package/dist/platform/{screenshot.js → capture/capture.js} +23 -509
  21. package/dist/platform/capture/index.js +10 -0
  22. package/dist/platform/capture/readiness.js +103 -0
  23. package/dist/platform/capture/summary.js +77 -0
  24. package/dist/platform/capture/target.js +5 -0
  25. package/dist/platform/capture/targets/studio-mobile.js +15 -0
  26. package/dist/platform/capture/targets/studio-web.js +5 -0
  27. package/dist/platform/domSummary.js +1 -1
  28. package/dist/resources/examples/mobile-app-shell-drawer.json +209 -0
  29. package/dist/resources/examples/mobile-app-shell-stack.json +123 -0
  30. package/dist/resources/examples/mobile-app-shell-tab.json +174 -0
  31. package/dist/resources/examples/mobile-list-detail-form.json +656 -0
  32. package/dist/resources/guides/component.md +6 -1
  33. package/dist/resources/guides/functions.md +1 -1
  34. package/dist/resources/guides/layout.md +11 -7
  35. package/dist/resources/guides/mobile.md +264 -0
  36. package/dist/resources/guides/page.md +55 -15
  37. package/dist/resources/guides/translation.md +1 -1
  38. package/dist/server.js +2 -0
  39. package/dist/tools/applications.js +60 -3
  40. package/dist/tools/components.js +50 -10
  41. package/dist/tools/designSchemaPatch.js +13 -4
  42. package/dist/tools/externalComponents.js +25 -4
  43. package/dist/tools/flows.js +9 -2
  44. package/dist/tools/layouts.js +41 -9
  45. package/dist/tools/mobile.js +203 -0
  46. package/dist/tools/pages.js +115 -35
  47. package/dist/tools/platformInfo.js +2 -1
  48. package/dist/tools/profileWrite.js +45 -0
  49. package/dist/tools/screenWrite.js +2 -2
  50. package/dist/tools/screens.js +9 -3
  51. package/package.json +1 -1
@@ -10,7 +10,8 @@ import { emptyWriteDiff, schemaOpDrops, unwrapProps, withWriteDiff, writeDiff }
10
10
  import { PlatformApiError } from '../platform/errors.js';
11
11
  import { existingMatch, ifExistsParam, listAll, withOutcome } from '../platform/ifExists.js';
12
12
  import { handle, toolResult, toolError } from '../platform/responses.js';
13
- import { StudioScreenshotter } from '../platform/screenshot.js';
13
+ import { StudioScreenshotter } from '../platform/capture/index.js';
14
+ import { captureTargetFor } from '../platform/capture/target.js';
14
15
  import { diffSummaries, summaryLines, summaryStats } from '../platform/domSummary.js';
15
16
  import { captureKey, imageHash, sharedCaptureMemory } from '../platform/screenshotMemory.js';
16
17
  import { summarizeLayout } from '../knowledge/layoutSummary.js';
@@ -22,6 +23,9 @@ import { dirname, resolve } from 'node:path';
22
23
  import { setPropTypeErrors } from '../knowledge/propTypes.js';
23
24
  import { designSnapshotInput } from '../knowledge/designSnapshot.js';
24
25
  import { discoverPage } from '../knowledge/pageDiscover.js';
26
+ import { applicationTypeField, resolveProfile, resolveProfileBySlug } from '../knowledge/platform/resolve.js';
27
+ import { resolveThemeNative } from '../knowledge/styleResolveNative.js';
28
+ import { boundFlowSteps, screenProfileWarnings } from './profileWrite.js';
25
29
  import { z } from 'zod';
26
30
  /**
27
31
  * Runtime paketindeki `appFunctions`/`pageFunctions` (UiFunctionRuntimeDto: id, name, parameters,
@@ -105,6 +109,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
105
109
  annotations: readOnly
106
110
  }, handle(config, async ({ project, environment, applicationId, pageId }) => {
107
111
  const page = await fetchPage(platform, project, environment, applicationId, pageId);
112
+ const resolved = await resolveProfile(platform, applicationId);
108
113
  const layout = (page.layout ?? {});
109
114
  return {
110
115
  id: page.id,
@@ -123,7 +128,8 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
123
128
  flows: (Array.isArray(page.flows) ? page.flows : [])
124
129
  .map(flow => ({ id: flow.id, name: flow.name, slug: flow.slug })),
125
130
  schemaNodes: countSchemaNodes(page.schema),
126
- updatedAt: page.audit?.updatedAt ?? page.audit?.createdAt
131
+ updatedAt: page.audit?.updatedAt ?? page.audit?.createdAt,
132
+ ...applicationTypeField(resolved)
127
133
  };
128
134
  }));
129
135
  server.registerTool('page_runtime_get', {
@@ -171,11 +177,17 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
171
177
  const styleSource = resolveStyles ? (theme ?? null) : undefined;
172
178
  const asNodes = (value) => (Array.isArray(value) ? value : []);
173
179
  const named = (value) => (Array.isArray(value) ? value : []).map(entry => ({ id: entry.id, name: entry.name, slug: entry.slug }));
180
+ const runtimeApplication = (body.application ?? {});
181
+ // Mobil uygulama React Native stiliyle çizilir (G4): rapor CSS kaskadı değil `shell`/`text`.
182
+ const nativeTheme = resolveStyles && runtimeApplication.type === 'mobile' ? resolveThemeNative((theme ?? undefined)) : undefined;
183
+ const nodeStyleSource = nativeTheme ? undefined : styleSource;
174
184
  return {
175
185
  page: { id: body.id, name: body.name, slug: body.slug, browserTitle: body.browserTitle, browserTitleKey: body.browserTitleKey, isHomepage: body.isHomepage },
186
+ ...(typeof runtimeApplication.type === 'string' ? { applicationType: runtimeApplication.type } : {}),
176
187
  events: body.events ?? [],
177
- layout: layout ? { id: layout.id, name: layout.name, views: asNodes(layout.views).map(node => summarize(node, styleSource)) } : null,
178
- schema: asNodes(body.schema).map(node => summarize(node, styleSource)),
188
+ layout: layout ? { id: layout.id, name: layout.name, views: asNodes(layout.views).map(node => summarize(node, nodeStyleSource, nativeTheme)) } : null,
189
+ schema: asNodes(body.schema).map(node => summarize(node, nodeStyleSource, nativeTheme)),
190
+ ...(nativeTheme ? { styleResolver: 'native' } : {}),
179
191
  widgets: named(body.widgets),
180
192
  flows: named(body.flows),
181
193
  variableKeys: Object.keys((body.variables ?? {})),
@@ -194,9 +206,11 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
194
206
  }
195
207
  }
196
208
  : {}),
197
- note: resolveStyles
198
- ? "schema, layout'taki slot'un İÇİNE yerleşerek çizilir (Kural 1f). resolvedStyle = componentStyles → preset → düğüm stili kaskadı (aibox'ta tipli grupların derlemesi TABAN katmandır); styleLayers hangi katmanların konuştuğunu söyler — styleLayers.componentPreset ise CSS DEĞİL, düğümün hangi bileşen preseti versiyonundan kurulduğunun işaretidir."
199
- : "schema, layout'taki slot'un İÇİNE yerleşerek çizilir (Kural 1f); tam gövde gerekirse view:'full'. Bir düğümün ekranda GERÇEKTEN aldığı stili görmek için resolveStyles:true."
209
+ note: nativeTheme
210
+ ? "Mobil uygulama: nativeStyle, mobil çizerin düğüme verdiği React Native stilidir — shell kutunun, text içine akan metnin stili (RN'de tipografi miras alınmaz); warnings mobilde çizilmeyen değerlerdir (MOB-002). Tema componentStyles/preset katmanı mobil çizerde henüz uygulanmıyor, bu yüzden rapora girmez."
211
+ : resolveStyles
212
+ ? "schema, layout'taki slot'un İÇİNE yerleşerek çizilir (Kural 1f). resolvedStyle = componentStyles → preset → düğüm stili kaskadı (aibox'ta tipli grupların derlemesi TABAN katmandır); styleLayers hangi katmanların konuştuğunu söyler — styleLayers.componentPreset ise CSS DEĞİL, düğümün hangi bileşen preseti versiyonundan kurulduğunun işaretidir."
213
+ : "schema, layout'taki slot'un İÇİNE yerleşerek çizilir (Kural 1f); tam gövde gerekirse view:'full'. Bir düğümün ekranda GERÇEKTEN aldığı stili görmek için resolveStyles:true."
200
214
  };
201
215
  }));
202
216
  const screenshotter = injectedScreenshotter ?? new StudioScreenshotter(config);
@@ -213,7 +227,15 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
213
227
  viewport: z.object({
214
228
  width: z.number().int().min(320).max(3840),
215
229
  height: z.number().int().min(240).max(2160)
216
- }).optional().describe('Varsayılan 1280x800 (deviceScaleFactor 1)'),
230
+ }).optional().describe('Varsayılan 1280x800 (deviceScaleFactor 1); mobil uygulamada varsayılan device. Verilirse device yok sayılır'),
231
+ device: z.union([
232
+ z.enum(['phone', 'tablet']),
233
+ z.object({
234
+ width: z.number().int().min(320).max(3840),
235
+ height: z.number().int().min(240).max(2160),
236
+ scale: z.number().min(1).max(3).optional()
237
+ })
238
+ ]).optional().describe("Yalnız MOBİL uygulama: cihaz ön ayarı — 'phone' 390x844 @2x (varsayılan), 'tablet' 820x1180 @2x, ya da { width, height, scale? }. Kare cihaz piksel yoğunluğunda çıkar (phone → 780x1688 px); raster küçültme için scale parametresi bununla çarpılır. Web uygulamasında reddedilir: dar ekran için viewport verin."),
217
239
  fullPage: z.boolean().optional().describe('true: kaydırılan kısım dahil tüm sayfa; varsayılan yalnız viewport karesi'),
218
240
  delayMs: z.number().int().min(0).max(60000).optional().describe("Hazırlık bütçesine EK süre (ms). Varsayılan bütçe 15 sn'dir ve kare sayfa hazır olur olmaz alınır (akışsız giriş ekranı ~1 sn, dashboard mount akışları bitince) — normalde vermeye gerek yok. Yalnız sayfa gerçekten uzun sürüyorsa (soğuk Vite derlemesi, ağır mount akışı) büyütün: bütçe dolduğunda sayfa hâlâ hazır değilse çağrı hatayla döner, kare alınmaz. Sayfanın AÇILMASI için beklenen 20 sn'lik navigasyon süresi de bu kadar uzar — soğuk Studio'da tek düğme budur (09-08 S-8)."),
219
241
  captureUnready: z.boolean().optional().describe("true: TEŞHİS kipi — bütçe dolduğunda sayfa hazır olmasa da kare alınır ve yanıt pageState: 'not-ready' taşır (iskelet/yarım kare görülebilir). Varsayılan false: yüklenmemiş sayfa çekilmez, hata döner."),
@@ -229,7 +251,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
229
251
  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"),
230
252
  ms: z.number().int().min(0).max(15000).optional().describe("'wait' için bekleme süresi")
231
253
  })).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. '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. '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)."),
232
- 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ı."),
254
+ 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)."),
233
255
  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."),
234
256
  format: z.enum(['png', 'jpeg']).optional().describe("Kare biçimi; varsayılan 'png'. Doğrulama karesi için 'jpeg' + quality 60 bağlamı küçültür (ölçüldü 2026-09-16, aynı sayfa: scale 1'de png 8639 B / jpeg60 8691 B — kazanç yok; scale 0.5'te png 3603 B / jpeg60 3035 B). Asıl kaldıraç ÖLÇEKTİR; renk ve kenar keskinliğinin kendisi inceleniyorsa png'de kalın."),
235
257
  quality: z.number().int().min(1).max(100).optional().describe("Yalnız format:'jpeg' ile: 1-100, doğrulama için 60 yeter. format 'png' iken verilirse çağrı reddedilir — PNG kayıpsızdır, kalite ayarı yoktur."),
@@ -244,7 +266,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
244
266
  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')
245
267
  },
246
268
  annotations: readOnly
247
- }, async ({ project, environment, applicationSlug, pageSlug, viewport, fullPage, delayMs, captureUnready, anonymous, asUser, cookies, layoutReport, actions, outPath, expectFailure, language, colorScheme, summary, image, format, quality, scale, clip, diff }) => {
269
+ }, async ({ project, environment, applicationSlug, pageSlug, viewport, device, fullPage, delayMs, captureUnready, anonymous, asUser, cookies, layoutReport, actions, outPath, expectFailure, language, colorScheme, summary, image, format, quality, scale, clip, diff }) => {
248
270
  try {
249
271
  if (anonymous === true && (asUser !== undefined || cookies !== undefined)) {
250
272
  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.");
@@ -288,7 +310,24 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
288
310
  }
289
311
  }
290
312
  const url = `${config.studioBaseUrl}/${lane}/${projectSlug}/${applicationSlug}/${pageSlug}${languageChoice ? `?lang=${languageChoice.code}` : ''}`;
291
- const size = viewport ?? { width: 1280, height: 800 };
313
+ // Hedef ve cihaz uygulamanın platform profilinden (G3): mobil uygulama react-native-web
314
+ // çizimidir, varsayılan kare telefon ön ayarıdır. Tip okunamazsa web davranışı aynen.
315
+ const resolved = await resolveProfileBySlug(platform, projectSlug, applicationSlug);
316
+ const target = captureTargetFor(resolved.profile.capture.target);
317
+ const mobile = resolved.profile.platform === 'mobile';
318
+ if (device !== undefined && !mobile) {
319
+ throw new PlatformApiError(`page_screenshot: device yalnız MOBİL uygulamada geçerlidir — '${applicationSlug}' ${resolved.known ? 'bir web uygulaması' : 'uygulamasının tipi okunamadı (web varsayıldı)'}. Web'in dar ekran kırılımını görmek için viewport verin (ör. { width: 390, height: 844 }).`);
320
+ }
321
+ const deviceName = typeof device === 'string' ? device : device === undefined && mobile ? resolved.profile.capture.defaultDevice : undefined;
322
+ const preset = typeof device === 'object'
323
+ ? { width: device.width, height: device.height, dpr: device.scale ?? 1 }
324
+ : deviceName === undefined ? undefined : resolved.profile.capture.devices[deviceName];
325
+ const appliedDevice = viewport === undefined && preset !== undefined ? preset : undefined;
326
+ const deviceNote = viewport !== undefined && device !== undefined
327
+ ? 'viewport verildiği için device yok sayıldı: kare verilen viewport ve deviceScaleFactor 1 ile çekildi.'
328
+ : undefined;
329
+ const size = viewport ?? (appliedDevice ? { width: appliedDevice.width, height: appliedDevice.height } : { width: 1280, height: 800 });
330
+ const dpr = appliedDevice && appliedDevice.dpr !== 1 ? appliedDevice.dpr : undefined;
292
331
  const extraCookies = Object.entries(cookies ?? {}).map(([name, value]) => ({ name, value }));
293
332
  const identity = anonymous === true ? 'anonymous' : asUser === undefined ? 'gateway' : 'asUser';
294
333
  const identityToken = anonymous ? undefined : resolveIdentity(asUser) ?? await platform.accessToken();
@@ -313,7 +352,9 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
313
352
  ...(quality === undefined ? {} : { quality }),
314
353
  ...(scale === undefined ? {} : { scale }),
315
354
  ...(clip === undefined ? {} : { clip: clip }),
316
- ...(actions === undefined ? {} : { actions: actions })
355
+ ...(actions === undefined ? {} : { actions: actions }),
356
+ ...(mobile ? { target } : {}),
357
+ ...(dpr === undefined ? {} : { dpr })
317
358
  });
318
359
  // Fark ÖZETTEN çıkar; `summary:false` verilse de fark için toplanmıştır (yanıta girmez).
319
360
  // Anahtar KİMLİĞİ ayırır (K8): 'asUser' etiketi yetmez — iki farklı üyenin jetonu aynı
@@ -384,6 +425,11 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
384
425
  ...(languageChoice?.note ? { languageNote: languageChoice.note } : {}),
385
426
  ...(colorScheme === undefined ? {} : { colorScheme }),
386
427
  viewport: size,
428
+ ...(appliedDevice === undefined ? {} : { device: typeof device === 'object' ? { ...device } : deviceName, deviceScaleFactor: appliedDevice.dpr }),
429
+ ...(deviceNote === undefined ? {} : { deviceNote }),
430
+ // Web karesinin yanıtı profil öncesiyle aynı kalır (G3 kısıtı); alan yalnız mobilde.
431
+ ...(mobile ? applicationTypeField(resolved) : {}),
432
+ ...(target.platformNote === undefined ? {} : { platformNote: target.platformNote }),
387
433
  fullPage: fullPage === true,
388
434
  identity,
389
435
  cookieNames,
@@ -535,19 +581,20 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
535
581
  annotations: readOnly
536
582
  }, handle(config, async ({ project, environment, applicationId, pageId, mode, componentId }) => {
537
583
  const page = await fetchPage(platform, project, environment, applicationId, pageId);
584
+ const typeField = applicationTypeField(await resolveProfile(platform, applicationId));
538
585
  const updatedAt = page.audit?.updatedAt ?? page.audit?.createdAt;
539
586
  const version = page.version;
540
587
  if (mode === 'full')
541
- return { version, updatedAt, schema: page.schema ?? [] };
588
+ return { version, updatedAt, ...typeField, schema: page.schema ?? [] };
542
589
  if (mode === 'subtree') {
543
590
  if (!componentId)
544
591
  throw new PlatformApiError("mode='subtree' için componentId zorunludur.");
545
592
  const subtree = findNode(page.schema, componentId);
546
593
  if (!subtree)
547
594
  throw new PlatformApiError(`Sayfada '${componentId}' id'li bir komponent yok; page_schema_get'i summary kipinde çağırıp hedefi doğrulayın.`);
548
- return { version, updatedAt, schema: subtree };
595
+ return { version, updatedAt, ...typeField, schema: subtree };
549
596
  }
550
- return { version, updatedAt, schema: (page.schema ?? []).map(node => summarize(node)) };
597
+ return { version, updatedAt, ...typeField, schema: (page.schema ?? []).map(node => summarize(node)) };
551
598
  }));
552
599
  server.registerTool('page_state', {
553
600
  title: 'Sayfa durumunu getir',
@@ -573,15 +620,20 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
573
620
  ...schemaOpValidationErrors(opRecords).map(issue => `PAGE-016 — ${issue}`),
574
621
  ...codePropTemplateErrors(opRecords).map(issue => `PAGE-032 — ${issue}`)
575
622
  ];
576
- const ordering = await orderByIndex(opRecords, () => fetchPage(platform, project, environment, applicationId, pageId));
623
+ const resolved = await resolveProfile(platform, applicationId);
624
+ const ordering = resolved.profile.layout.orderBy === 'block-position'
625
+ ? await orderByIndex(opRecords, () => fetchPage(platform, project, environment, applicationId, pageId))
626
+ : { ops: opRecords, moved: [], warnings: [] };
627
+ const profileWarnings = await screenProfileWarnings(platform, resolved, project, environment, applicationId, { kind: 'page', ops: opRecords });
577
628
  const response = ((await platform.tenant('POST', project, `/pages/${applicationId}/${pageId}/schema/validate`, {
578
629
  environment,
579
630
  body: { ops: [...allOps.filter(entry => entry.op === 'resize_component').flatMap(resizeServerOps), ...ordering.ops] }
580
631
  })) ?? {});
581
632
  if (validationIssues.length > 0)
582
633
  response.validationWarnings = validationIssues;
583
- if (ordering.warnings.length > 0)
584
- response.layoutWarnings = ordering.warnings;
634
+ if (ordering.warnings.length > 0 || profileWarnings.length > 0)
635
+ response.layoutWarnings = [...ordering.warnings, ...profileWarnings];
636
+ Object.assign(response, applicationTypeField(resolved));
585
637
  return withNormalization(response, [
586
638
  ...componentDefaultsNote(normalized.components),
587
639
  ...validationNote(normalized.validations),
@@ -669,7 +721,12 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
669
721
  retargeted = retargetRootAdds(opRecords, PAGE_CONTAINER_ELEMENT_ID);
670
722
  }
671
723
  }
672
- const ordering = await orderByIndex(opRecords, readPage);
724
+ const resolved = await resolveProfile(platform, applicationId);
725
+ const blocksMeaningful = resolved.profile.layout.blocksMeaningful;
726
+ // Mobil (dizi sırası): index platformun kendi add/move index'idir, konum çevirisi yapılmaz.
727
+ const ordering = resolved.profile.layout.orderBy === 'block-position'
728
+ ? await orderByIndex(opRecords, readPage)
729
+ : { ops: opRecords, moved: [], warnings: [] };
673
730
  const sentOps = ordering.ops;
674
731
  // Akış silen op'lar yamadan ÖNCE hesaplanır: uyarı sonradan geldiğinde akış zaten kalıcı
675
732
  // silinmiş oluyordu ve "önce yedekle" tavsiyesi uygulanamıyordu (09-05 koşusu, G-21).
@@ -729,10 +786,10 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
729
786
  // görünür, iç içeliği ancak hedef parent zinciri belli olunca anlarsın.
730
787
  nestedAfterOps = nestedRepeaterNodes(simulated);
731
788
  layoutWarnings = [
732
- ...pageContainerPositionWarning(page.schema),
733
- ...verticalGapWarnings(simulated, 'PAGE-010 (uyarı)', touched),
789
+ ...(blocksMeaningful ? pageContainerPositionWarning(page.schema) : []),
790
+ ...(blocksMeaningful ? verticalGapWarnings(simulated, 'PAGE-010 (uyarı)', touched) : []),
734
791
  ...stringStyleWarning(stringStyleNodes(simulated)),
735
- ...inertFlexPropWarning(inertFlexPropNodes(simulated)),
792
+ ...(resolved.profile.layout.modes.has('grid') ? inertFlexPropWarning(inertFlexPropNodes(simulated)) : []),
736
793
  ...flexSizeWarnings(simulated, 'PAGE-017 (uyarı)', 'PAGE-018 (uyarı)'),
737
794
  ...spacingLayerWarnings(simulated, 'PAGE-029 (uyarı)', 'PAGE-030 (uyarı)')
738
795
  ];
@@ -747,7 +804,12 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
747
804
  const resized = resizeOps.length > 0 ? planResizes((await readPage()).schema ?? [], resizeOps) : { serverOps: [], applied: [], warnings: [] };
748
805
  const propTypes = propTypeOps.length > 0 ? planPropTypes((await readPage()).schema ?? [], propTypeOps) : { serverOps: [], applied: [] };
749
806
  const serverOps = [...removeEventOps, ...resized.serverOps, ...propTypes.serverOps, ...sentOps];
750
- layoutWarnings = [...ordering.warnings, ...layoutWarnings];
807
+ const profileWarnings = await screenProfileWarnings(platform, resolved, project, environment, applicationId, {
808
+ kind: 'page',
809
+ ops: [...opRecords, ...propTypeOps],
810
+ steps: await boundFlowSteps(platform, resolved, project, environment, 'page', applicationId, pageId, opRecords)
811
+ });
812
+ layoutWarnings = [...ordering.warnings, ...layoutWarnings, ...profileWarnings];
751
813
  // Tek istek, tek sürüm: öykünülen op'lar 2026-09-14'e kadar ayrı GET + tam PUT çiftleriydi ve
752
814
  // yamadan ÖNCE yazılıyordu — PATCH reddedilse bile kalıcıydılar. Artık hepsi aynı atomik PATCH'te.
753
815
  // baseVersion yalnız biliniyorsa gider (ajanın verdiği ya da bu çağrıda zaten okunan sayfanınki):
@@ -801,6 +863,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
801
863
  opDrops = emptyWriteDiff();
802
864
  }
803
865
  }
866
+ Object.assign(response, applicationTypeField(resolved));
804
867
  return withWriteDiff(withNormalization(response, [
805
868
  ...rootAddsNote(retargeted, PAGE_CONTAINER_ELEMENT_ID),
806
869
  ...componentDefaultsNote(normalized.components),
@@ -845,16 +908,21 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
845
908
  },
846
909
  annotations: writes
847
910
  }, handle(config, async ({ project, environment, applicationId, name, slug, browserTitle, browserTitleKey, layoutId, roleIds, middlewareIds, events, schema, view, ifExists }) => {
848
- const container = ensurePageContainer(schema ?? []);
911
+ const resolved = await resolveProfile(platform, applicationId);
912
+ const blocksMeaningful = resolved.profile.layout.blocksMeaningful;
913
+ const container = ensurePageContainer(schema ?? [], resolved.profile.layout.rootContainer);
849
914
  assertScreenWriteGuards('page', { schema: container.schema, events });
850
915
  const stringStyles = stringStyleNodes(container.schema);
851
916
  const fixed = normalizeComponentDefaults(container.schema);
852
- const reset = resetPageContainerPosition(container.schema);
917
+ // Mobil çizer blokları okumaz (D3): kök konumu sıfırlama, blok sıralama ve dikey boşluk
918
+ // uyarısı yalnız ızgaranın anlamlı olduğu profilde koşar.
919
+ const reset = blocksMeaningful ? resetPageContainerPosition(container.schema) : [];
853
920
  const catalogNotes = (schema ?? []).length > 0
854
921
  ? await screenCatalogNotes(platform, project, environment, applicationId, container.schema)
855
922
  : [];
856
- const moved = reorderSchemaBlocks(container.schema);
857
- const gaps = (schema ?? []).length > 0 ? verticalGapWarnings(container.schema, 'PAGE-010 (uyarı)') : [];
923
+ const moved = blocksMeaningful ? reorderSchemaBlocks(container.schema) : [];
924
+ const gaps = (schema ?? []).length > 0 && blocksMeaningful ? verticalGapWarnings(container.schema, 'PAGE-010 (uyarı)') : [];
925
+ const profileWarnings = await screenProfileWarnings(platform, resolved, project, environment, applicationId, { kind: 'page', nodes: container.schema });
858
926
  const existing = await existingMatch({
859
927
  code: 'PAGE-035',
860
928
  noun: 'sayfa',
@@ -882,12 +950,13 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
882
950
  await platform.tenant('PUT', project, `/pages/${applicationId}/${pageId}`, { environment, body });
883
951
  const updateWarnings = schema === undefined
884
952
  ? []
885
- : [...gaps, ...stringStyleWarning(stringStyles), ...inertFlexPropWarning(inertFlexPropNodes(container.schema)), ...flexSizeWarnings(container.schema, 'PAGE-017 (uyarı)', 'PAGE-018 (uyarı)'), ...spacingLayerWarnings(container.schema, 'PAGE-029 (uyarı)', 'PAGE-030 (uyarı)')];
953
+ : [...gaps, ...stringStyleWarning(stringStyles), ...(resolved.profile.layout.modes.has('grid') ? inertFlexPropWarning(inertFlexPropNodes(container.schema)) : []), ...flexSizeWarnings(container.schema, 'PAGE-017 (uyarı)', 'PAGE-018 (uyarı)'), ...spacingLayerWarnings(container.schema, 'PAGE-029 (uyarı)', 'PAGE-030 (uyarı)'), ...profileWarnings];
886
954
  const summary = {
887
955
  id: pageId,
888
956
  outcome: 'updated',
889
957
  changed,
890
- preserved: { schemaNodes: countSchemaNodes(body.schema), events: body.events.length }
958
+ preserved: { schemaNodes: countSchemaNodes(body.schema), events: body.events.length },
959
+ ...applicationTypeField(resolved)
891
960
  };
892
961
  if (updateWarnings.length > 0)
893
962
  summary.layoutWarnings = updateWarnings;
@@ -920,10 +989,13 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
920
989
  saved.schema = saved.schema.map(node => summarize(node));
921
990
  }
922
991
  const normalized = withWriteDiff(withNormalization(withOutcome(response, undefined), [...container.fixes, ...reset, ...componentDefaultsNote(fixed), ...catalogNotes, ...reorderNote(moved)]), drops);
923
- const createWarnings = [...gaps, ...stringStyleWarning(stringStyles), ...inertFlexPropWarning(inertFlexPropNodes(container.schema)), ...flexSizeWarnings(container.schema, 'PAGE-017 (uyarı)', 'PAGE-018 (uyarı)'), ...spacingLayerWarnings(container.schema, 'PAGE-029 (uyarı)', 'PAGE-030 (uyarı)')];
924
- if (createWarnings.length === 0 || !normalized || typeof normalized !== 'object')
992
+ const createWarnings = [...gaps, ...stringStyleWarning(stringStyles), ...(resolved.profile.layout.modes.has('grid') ? inertFlexPropWarning(inertFlexPropNodes(container.schema)) : []), ...flexSizeWarnings(container.schema, 'PAGE-017 (uyarı)', 'PAGE-018 (uyarı)'), ...spacingLayerWarnings(container.schema, 'PAGE-029 (uyarı)', 'PAGE-030 (uyarı)'), ...profileWarnings];
993
+ const typeField = applicationTypeField(resolved);
994
+ if (!normalized || typeof normalized !== 'object')
925
995
  return normalized;
926
- return { ...normalized, layoutWarnings: createWarnings };
996
+ if (createWarnings.length === 0)
997
+ return Object.keys(typeField).length === 0 ? normalized : { ...normalized, ...typeField };
998
+ return { ...normalized, ...typeField, layoutWarnings: createWarnings };
927
999
  }));
928
1000
  server.registerTool('page_update', {
929
1001
  title: 'Sayfa üstbilgisini güncelle',
@@ -948,13 +1020,21 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
948
1020
  const overrides = Object.fromEntries(Object.entries(changes).filter(([, value]) => value !== undefined));
949
1021
  assertScreenWriteGuards('page', { schema: overrides.schema, events: overrides.events });
950
1022
  const current = await fetchHost(platform, 'page', project, environment, applicationId, pageId);
1023
+ const resolved = overrides.schema === undefined ? undefined : await resolveProfile(platform, applicationId);
1024
+ const blocksMeaningful = resolved?.profile.layout.blocksMeaningful ?? true;
951
1025
  const fixed = normalizeComponentDefaults(overrides.schema);
952
- const reset = overrides.schema === undefined ? [] : resetPageContainerPosition(overrides.schema);
1026
+ const reset = overrides.schema === undefined || !blocksMeaningful ? [] : resetPageContainerPosition(overrides.schema);
953
1027
  const catalogNotes = await screenCatalogNotes(platform, project, environment, applicationId, overrides.schema);
954
- const moved = overrides.schema === undefined ? [] : reorderSchemaBlocks(overrides.schema);
1028
+ const moved = overrides.schema === undefined || !blocksMeaningful ? [] : reorderSchemaBlocks(overrides.schema);
955
1029
  const gaps = overrides.schema === undefined
956
1030
  ? []
957
- : [...verticalGapWarnings(overrides.schema, 'PAGE-010 (uyarı)'), ...inertFlexPropWarning(inertFlexPropNodes(overrides.schema)), ...flexSizeWarnings(overrides.schema, 'PAGE-017 (uyarı)', 'PAGE-018 (uyarı)'), ...spacingLayerWarnings(overrides.schema, 'PAGE-029 (uyarı)', 'PAGE-030 (uyarı)')];
1031
+ : [
1032
+ ...(blocksMeaningful ? verticalGapWarnings(overrides.schema, 'PAGE-010 (uyarı)') : []),
1033
+ ...(resolved?.profile.layout.modes.has('grid') ?? true ? inertFlexPropWarning(inertFlexPropNodes(overrides.schema)) : []),
1034
+ ...flexSizeWarnings(overrides.schema, 'PAGE-017 (uyarı)', 'PAGE-018 (uyarı)'),
1035
+ ...spacingLayerWarnings(overrides.schema, 'PAGE-029 (uyarı)', 'PAGE-030 (uyarı)'),
1036
+ ...(resolved ? await screenProfileWarnings(platform, resolved, project, environment, applicationId, { kind: 'page', nodes: overrides.schema }) : [])
1037
+ ];
958
1038
  const body = pageUpdateBody(current, overrides);
959
1039
  await platform.tenant('PUT', project, `/pages/${applicationId}/${pageId}`, { environment, body });
960
1040
  const summary = {
@@ -64,7 +64,8 @@ const guideRouting = {
64
64
  microfunction: 'kendi runtime\'ında koşan Node/Python fonksiyonu: protokol, yayın döngüsü, step kullanımı',
65
65
  entity: "alan tipleri, ilişkiler, hazır entity'ler",
66
66
  page: 'sayfa/flow işleri; flex düzen prop grubu ve boyut modları (Kural 1f-2)',
67
- layout: 'yerleşim kabuğu: tek slot, views, mobil yığılma, widthType fill/fixed/auto',
67
+ layout: 'yerleşim kabuğu: tek slot, views, dar ekranda yığılma, widthType fill/fixed/auto',
68
+ mobile: "mobil UYGULAMA (type:'mobile'): kurulum sırası (mobile_app_create), flex-only yerleşim, tipli stil, sekmeler (properties.mobile.tabs) ve çekmece, adım sözlüğü, MOB-* bulguları, page_screenshot device",
68
69
  component: 'komponent modeli, olay sözleşmeleri, appearance boyut sözlüğü (widthString/heightString)',
69
70
  html: 'hazır komponent yetmediğinde tek seferlik kaçış kapısı (Mustache, fireEvent, script kuralları)',
70
71
  'external-component': 'kalıcı ihtiyaçta kendi komponentini yazıp yayınlama',
@@ -0,0 +1,45 @@
1
+ import { findingLines, profileFindings } from '../knowledge/platform/lint.js';
2
+ import { resolveProfile } from '../knowledge/platform/resolve.js';
3
+ import { fetchCatalogSlugs } from './components.js';
4
+ import { flowPath } from './screens.js';
5
+ /**
6
+ * Sayfa/layout/widget yazma yollarının profil kapısı: mobil profilde `MOB-*` bulgularını
7
+ * `layoutWarnings[]` satırı olarak döner. Web profilinde HİÇBİR istek atmaz ve boş dizi döner —
8
+ * web yanıtı profil öncesiyle aynı kalır (G1 golden).
9
+ */
10
+ export async function screenProfileWarnings(platform, resolved, project, environment, applicationId, input) {
11
+ if (resolved.profile.platform === 'web')
12
+ return [];
13
+ const catalog = await fetchCatalogSlugs(platform, project, environment, applicationId).catch(() => undefined);
14
+ return findingLines(profileFindings(resolved.profile, { ...input, ...(catalog ? { catalog } : {}) }));
15
+ }
16
+ /**
17
+ * `set_event` ile bağlanan akışların adımları (MOB-005). Akış gövdeleri şemada değil ayrı uçtadır;
18
+ * okunamazsa bulgu üretilmez, yazım yine geçer.
19
+ */
20
+ export async function boundFlowSteps(platform, resolved, project, environment, host, applicationId, hostId, ops) {
21
+ if (resolved.profile.platform === 'web')
22
+ return [];
23
+ const flowIds = new Set(ops.filter(entry => entry.op === 'set_event').map(entry => String(entry.eventValue ?? '')).filter(id => id.length > 0));
24
+ if (flowIds.size === 0)
25
+ return [];
26
+ try {
27
+ const hosted = await platform.tenant('GET', project, flowPath(host, applicationId, hostId), { environment });
28
+ return (hosted?.data ?? [])
29
+ .filter(flow => flowIds.has(String(flow.id ?? '')))
30
+ .flatMap(flow => [...(Array.isArray(flow.steps) ? flow.steps : []), ...(Array.isArray(flow.errorSteps) ? flow.errorSteps : [])]);
31
+ }
32
+ catch {
33
+ return [];
34
+ }
35
+ }
36
+ /**
37
+ * Akış yazma yollarının profil kapısı (MOB-005): mobil uygulamada `download`/`setcookie` adımının
38
+ * davranış farkını `platformWarnings`'e yazar. Web profilinde istek atmaz, boş döner.
39
+ */
40
+ export async function flowProfileWarnings(platform, applicationId, steps) {
41
+ const resolved = await resolveProfile(platform, applicationId);
42
+ if (resolved.profile.platform === 'web')
43
+ return [];
44
+ return findingLines(profileFindings(resolved.profile, { kind: 'page', steps }));
45
+ }
@@ -1,4 +1,4 @@
1
- import { invalidScreenEventNames, invalidWidgetTypeNodes, nestedRepeaterNodes, nestedRepeaterMessage, blockSizeNote, heightTypeNote, sizeStringNote } from '../knowledge/normalize.js';
1
+ import { invalidScreenEventNames, invalidWidgetTypeNodes, nestedRepeaterNodes, nestedRepeaterMessage, blockSizeNote, heightTypeNote, sizeStringNote, droppableStatesNote } from '../knowledge/normalize.js';
2
2
  import { FUNCTIONS_NAMESPACE } from '../knowledge/functionGuards.js';
3
3
  import { applyCatalogLayoutRules } from './components.js';
4
4
  import { PlatformApiError } from '../platform/errors.js';
@@ -65,5 +65,5 @@ export async function screenCatalogNotes(platform, project, environment, applica
65
65
  if (!Array.isArray(nodes) || nodes.length === 0)
66
66
  return [];
67
67
  const catalog = await applyCatalogLayoutRules(platform, project, environment, applicationId, nodes);
68
- return [...blockSizeNote(catalog.clamped), ...heightTypeNote(catalog.heights), ...sizeStringNote(catalog.sizes)];
68
+ return [...blockSizeNote(catalog.clamped), ...heightTypeNote(catalog.heights), ...sizeStringNote(catalog.sizes), ...droppableStatesNote(catalog.states)];
69
69
  }
@@ -1,5 +1,6 @@
1
1
  import { PlatformApiError } from '../platform/errors.js';
2
2
  import { resolveNodeStyleReport } from '../knowledge/styleResolve.js';
3
+ import { nodeNativeStyle } from '../knowledge/styleResolveNative.js';
3
4
  import { z } from 'zod';
4
5
  export const hostParam = z.enum(['page', 'layout', 'component'])
5
6
  .describe("Akışın barındırıcısı: page | layout | component (widget de component'tir)");
@@ -13,21 +14,26 @@ export const modeParam = z.enum(['summary', 'subtree', 'full']).optional().descr
13
14
  * `styleSource` verilirse (undefined DEĞİLSE) her düğüme efektif stil raporu da
14
15
  * yazılır: tema componentStyles → preset → düğümün kendi stili kaskadı.
15
16
  */
16
- export function summarize(node, styleSource) {
17
+ export function summarize(node, styleSource, nativeTheme) {
17
18
  const summary = {
18
19
  id: node.id,
19
20
  elementId: node.elementId,
20
21
  name: node.name,
21
22
  type: node.type
22
23
  };
23
- if (styleSource !== undefined) {
24
+ // Mobil uygulama: stil CSS değil React Native nesnesidir (`shell` kutu, `text` metin).
25
+ if (nativeTheme !== undefined) {
26
+ const { shell, text, warnings } = nodeNativeStyle(node, nativeTheme);
27
+ summary.nativeStyle = { shell, text, ...(warnings.length > 0 ? { warnings } : {}) };
28
+ }
29
+ else if (styleSource !== undefined) {
24
30
  const report = resolveNodeStyleReport(node, styleSource ?? undefined);
25
31
  if (report.resolvedStyle !== undefined)
26
32
  summary.resolvedStyle = report.resolvedStyle;
27
33
  summary.styleLayers = report.styleLayers;
28
34
  }
29
35
  if (Array.isArray(node.children) && node.children.length > 0) {
30
- summary.children = node.children.map(child => summarize(child, styleSource));
36
+ summary.children = node.children.map(child => summarize(child, styleSource, nativeTheme));
31
37
  }
32
38
  return summary;
33
39
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appaflytech/zeyrek-ai-mcp",
3
- "version": "0.0.21",
3
+ "version": "0.0.22",
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",