@appaflytech/zeyrek-ai-mcp 0.0.18 → 0.0.20
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/README.md +13 -14
- package/dist/config.js +2 -0
- package/dist/knowledge/componentCatalog.js +204 -836
- package/dist/knowledge/componentEvents.js +75 -368
- package/dist/knowledge/mobileComponentCatalog.js +331 -0
- package/dist/knowledge/mobileComponentEvents.js +50 -0
- package/dist/knowledge/normalize.js +63 -66
- package/dist/knowledge/platform/lint.js +205 -0
- package/dist/knowledge/platform/mobile.js +78 -0
- package/dist/knowledge/platform/mobileRules.js +121 -0
- package/dist/knowledge/platform/mobileTabs.js +108 -0
- package/dist/knowledge/platform/profile.js +20 -0
- package/dist/knowledge/platform/resolve.js +122 -0
- package/dist/knowledge/platform/web.js +38 -0
- package/dist/knowledge/rules.js +6 -29
- package/dist/knowledge/styleResolve.js +1 -1
- package/dist/knowledge/styleResolveNative.js +410 -0
- package/dist/knowledge/themeResolve.js +3 -3
- package/dist/platform/capture/actions.js +333 -0
- package/dist/platform/capture/browser.js +1 -0
- package/dist/platform/{screenshot.js → capture/capture.js} +23 -509
- package/dist/platform/capture/index.js +10 -0
- package/dist/platform/capture/readiness.js +103 -0
- package/dist/platform/capture/summary.js +77 -0
- package/dist/platform/capture/target.js +5 -0
- package/dist/platform/capture/targets/studio-mobile.js +15 -0
- package/dist/platform/capture/targets/studio-web.js +5 -0
- package/dist/platform/domSummary.js +1 -1
- package/dist/platform/responses.js +1 -2
- package/dist/platform/strictArguments.js +2 -22
- package/dist/platform/writeDiff.js +31 -58
- package/dist/resources/examples/mobile-app-shell-drawer.json +209 -0
- package/dist/resources/examples/mobile-app-shell-stack.json +123 -0
- package/dist/resources/examples/mobile-app-shell-tab.json +174 -0
- package/dist/resources/examples/mobile-list-detail-form.json +656 -0
- package/dist/resources/guides/component.md +6 -9
- package/dist/resources/guides/functions.md +3 -32
- package/dist/resources/guides/getting-started.md +0 -35
- package/dist/resources/guides/layout.md +11 -7
- package/dist/resources/guides/mobile.md +215 -0
- package/dist/resources/guides/page.md +71 -49
- package/dist/resources/guides/translation.md +6 -5
- package/dist/resources/schema-fingerprint.json +2 -2
- package/dist/resources/schemas/GlobalFunctionUpdateResultDto.json +0 -18
- package/dist/server.js +2 -0
- package/dist/tools/applications.js +60 -3
- package/dist/tools/components.js +50 -10
- package/dist/tools/designSchemaPatch.js +18 -13
- package/dist/tools/externalComponents.js +25 -4
- package/dist/tools/flows.js +51 -38
- package/dist/tools/functions.js +10 -20
- package/dist/tools/layouts.js +41 -9
- package/dist/tools/microservices.js +11 -39
- package/dist/tools/mobile.js +203 -0
- package/dist/tools/pages.js +127 -95
- package/dist/tools/platformInfo.js +2 -1
- package/dist/tools/profileWrite.js +45 -0
- package/dist/tools/schemaOps.js +2 -2
- package/dist/tools/screenWrite.js +2 -2
- package/dist/tools/screens.js +9 -3
- package/dist/tools/workflows.js +12 -39
- package/package.json +2 -7
- package/dist/platform/diagnostics.js +0 -85
- package/dist/platform/ifExists.js +0 -129
- package/dist/platform/writeView.js +0 -17
package/dist/tools/pages.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { guid, projectParam, environmentParam, pagingParams, readOnly, writes, updates, destructive, microOps, isBranchName } from './common.js';
|
|
2
|
-
import { orphanedFlowWarnings, orphanedFlowPlan, orphanedFlowMessage,
|
|
2
|
+
import { orphanedFlowWarnings, orphanedFlowPlan, orphanedFlowMessage, staleSetterWarnings, lostSetterTargets, normalizeComponentDefaults, normalizeSchemaOps, schemaOpValidationErrors, codePropTemplateErrors, validationNote, componentDefaultsNote, replacePropNote, ensurePageContainer, retargetRootAdds, rootAddsNote, flexSizeWarnings, spacingLayerWarnings, reorderSchemaBlocks, reorderNote, nestedRepeaterNodes, nestedRepeaterMessage, stringStyleNodes, stringStyleWarning, inertFlexPropNodes, inertFlexPropWarning, FLEX_LAYOUT_PROP_NAMES, resetPageContainerPosition, pageContainerPositionWarning, verticalGapWarnings, flexIndexOrder, flexIndexOrderNote, withNormalization, parseVariableEntries, PAGE_CONTAINER_ELEMENT_ID } from '../knowledge/normalize.js';
|
|
3
3
|
import { functionSignature } from '../knowledge/functionGuards.js';
|
|
4
4
|
import { fetchHost, pageUpdateBody, schemaParam, eventsParam, unwrap, modeParam, summarize, findNode, flowPath } from './screens.js';
|
|
5
5
|
import { fetchComponentEventNames } from './components.js';
|
|
6
6
|
import { assertScreenWriteGuards, screenCatalogNotes } from './screenWrite.js';
|
|
7
7
|
import { opsParam, writeViewParam, outlineWriteResponse, unknownEventOps, collectTypes, simulateLayoutOps, planEventRemovals, resizeServerOps, planResizes, planPropTypes, rejectedOpsMessage } from './schemaOps.js';
|
|
8
8
|
import { formBindingWarnings, formSubmitButtonWarnings } from '../knowledge/coverage.js';
|
|
9
|
-
import {
|
|
9
|
+
import { schemaOpDrops, silentlyDropped, unwrapProps, withWriteDiff } from '../platform/writeDiff.js';
|
|
10
10
|
import { PlatformApiError } from '../platform/errors.js';
|
|
11
|
-
import { existingMatch, ifExistsParam, listAll, withOutcome } from '../platform/ifExists.js';
|
|
12
11
|
import { handle, toolResult, toolError } from '../platform/responses.js';
|
|
13
|
-
import { StudioScreenshotter } from '../platform/
|
|
12
|
+
import { StudioScreenshotter } from '../platform/capture/index.js';
|
|
13
|
+
import { captureTargetFor } from '../platform/capture/target.js';
|
|
14
14
|
import { diffSummaries, summaryLines, summaryStats } from '../platform/domSummary.js';
|
|
15
15
|
import { captureKey, imageHash, sharedCaptureMemory } from '../platform/screenshotMemory.js';
|
|
16
16
|
import { summarizeLayout } from '../knowledge/layoutSummary.js';
|
|
@@ -22,6 +22,9 @@ import { dirname, resolve } from 'node:path';
|
|
|
22
22
|
import { setPropTypeErrors } from '../knowledge/propTypes.js';
|
|
23
23
|
import { designSnapshotInput } from '../knowledge/designSnapshot.js';
|
|
24
24
|
import { discoverPage } from '../knowledge/pageDiscover.js';
|
|
25
|
+
import { applicationTypeField, resolveProfile, resolveProfileBySlug } from '../knowledge/platform/resolve.js';
|
|
26
|
+
import { resolveThemeNative } from '../knowledge/styleResolveNative.js';
|
|
27
|
+
import { boundFlowSteps, screenProfileWarnings } from './profileWrite.js';
|
|
25
28
|
import { z } from 'zod';
|
|
26
29
|
/**
|
|
27
30
|
* Runtime paketindeki `appFunctions`/`pageFunctions` (UiFunctionRuntimeDto: id, name, parameters,
|
|
@@ -56,10 +59,6 @@ async function orderByIndex(ops, readPage) {
|
|
|
56
59
|
return { ops, moved: [], warnings: [] };
|
|
57
60
|
}
|
|
58
61
|
}
|
|
59
|
-
/** Şema ağacındaki TÜM düğümler (kök + iç içe çocuklar); page_get ve page_create özeti aynı sayımı kullanır. */
|
|
60
|
-
function countSchemaNodes(nodes) {
|
|
61
|
-
return (nodes ?? []).reduce((total, node) => total + 1 + countSchemaNodes(node.children), 0);
|
|
62
|
-
}
|
|
63
62
|
async function fetchPage(platform, project, environment, applicationId, pageId) {
|
|
64
63
|
const response = await platform.tenant('GET', project, `/pages/${applicationId}/${pageId}`, { environment });
|
|
65
64
|
return response?.data ?? {};
|
|
@@ -72,7 +71,7 @@ async function fetchPage(platform, project, environment, applicationId, pageId)
|
|
|
72
71
|
*/
|
|
73
72
|
export const SERVER_PAGE_OPS = [
|
|
74
73
|
'replace_prop', 'set_binding', 'set_prop_type', 'add_component', 'remove_component', 'move_component', 'reorder', 'set_block',
|
|
75
|
-
'set_appearance', 'set_states', 'set_event', '
|
|
74
|
+
'set_appearance', 'set_states', 'set_event', 'remove_event', 'set_validations', 'set_state_binding', 'remove_state_binding', 'batch'
|
|
76
75
|
];
|
|
77
76
|
export function registerPageTools(server, platform, injectedScreenshotter, injectedMemory) {
|
|
78
77
|
const config = platform.config;
|
|
@@ -105,7 +104,9 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
105
104
|
annotations: readOnly
|
|
106
105
|
}, handle(config, async ({ project, environment, applicationId, pageId }) => {
|
|
107
106
|
const page = await fetchPage(platform, project, environment, applicationId, pageId);
|
|
107
|
+
const resolved = await resolveProfile(platform, applicationId);
|
|
108
108
|
const layout = (page.layout ?? {});
|
|
109
|
+
const countNodes = (nodes) => (nodes ?? []).reduce((total, node) => total + 1 + countNodes(node.children), 0);
|
|
109
110
|
return {
|
|
110
111
|
id: page.id,
|
|
111
112
|
name: page.name,
|
|
@@ -122,8 +123,9 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
122
123
|
events: page.events ?? [],
|
|
123
124
|
flows: (Array.isArray(page.flows) ? page.flows : [])
|
|
124
125
|
.map(flow => ({ id: flow.id, name: flow.name, slug: flow.slug })),
|
|
125
|
-
schemaNodes:
|
|
126
|
-
updatedAt: page.audit?.updatedAt ?? page.audit?.createdAt
|
|
126
|
+
schemaNodes: countNodes(page.schema),
|
|
127
|
+
updatedAt: page.audit?.updatedAt ?? page.audit?.createdAt,
|
|
128
|
+
...applicationTypeField(resolved)
|
|
127
129
|
};
|
|
128
130
|
}));
|
|
129
131
|
server.registerTool('page_runtime_get', {
|
|
@@ -171,11 +173,17 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
171
173
|
const styleSource = resolveStyles ? (theme ?? null) : undefined;
|
|
172
174
|
const asNodes = (value) => (Array.isArray(value) ? value : []);
|
|
173
175
|
const named = (value) => (Array.isArray(value) ? value : []).map(entry => ({ id: entry.id, name: entry.name, slug: entry.slug }));
|
|
176
|
+
const runtimeApplication = (body.application ?? {});
|
|
177
|
+
// Mobil uygulama React Native stiliyle çizilir (G4): rapor CSS kaskadı değil `shell`/`text`.
|
|
178
|
+
const nativeTheme = resolveStyles && runtimeApplication.type === 'mobile' ? resolveThemeNative((theme ?? undefined)) : undefined;
|
|
179
|
+
const nodeStyleSource = nativeTheme ? undefined : styleSource;
|
|
174
180
|
return {
|
|
175
181
|
page: { id: body.id, name: body.name, slug: body.slug, browserTitle: body.browserTitle, browserTitleKey: body.browserTitleKey, isHomepage: body.isHomepage },
|
|
182
|
+
...(typeof runtimeApplication.type === 'string' ? { applicationType: runtimeApplication.type } : {}),
|
|
176
183
|
events: body.events ?? [],
|
|
177
|
-
layout: layout ? { id: layout.id, name: layout.name, views: asNodes(layout.views).map(node => summarize(node,
|
|
178
|
-
schema: asNodes(body.schema).map(node => summarize(node,
|
|
184
|
+
layout: layout ? { id: layout.id, name: layout.name, views: asNodes(layout.views).map(node => summarize(node, nodeStyleSource, nativeTheme)) } : null,
|
|
185
|
+
schema: asNodes(body.schema).map(node => summarize(node, nodeStyleSource, nativeTheme)),
|
|
186
|
+
...(nativeTheme ? { styleResolver: 'native' } : {}),
|
|
179
187
|
widgets: named(body.widgets),
|
|
180
188
|
flows: named(body.flows),
|
|
181
189
|
variableKeys: Object.keys((body.variables ?? {})),
|
|
@@ -194,9 +202,11 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
194
202
|
}
|
|
195
203
|
}
|
|
196
204
|
: {}),
|
|
197
|
-
note:
|
|
198
|
-
? "
|
|
199
|
-
:
|
|
205
|
+
note: nativeTheme
|
|
206
|
+
? "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."
|
|
207
|
+
: resolveStyles
|
|
208
|
+
? "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."
|
|
209
|
+
: "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
210
|
};
|
|
201
211
|
}));
|
|
202
212
|
const screenshotter = injectedScreenshotter ?? new StudioScreenshotter(config);
|
|
@@ -204,7 +214,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
204
214
|
const maxInlinePngBytes = 5 * 1024 * 1024;
|
|
205
215
|
server.registerTool('page_screenshot', {
|
|
206
216
|
title: 'Sayfanın ekran görüntüsünü al',
|
|
207
|
-
description: "
|
|
217
|
+
description: "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. 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).",
|
|
208
218
|
inputSchema: {
|
|
209
219
|
project: projectParam,
|
|
210
220
|
environment: environmentParam,
|
|
@@ -213,7 +223,15 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
213
223
|
viewport: z.object({
|
|
214
224
|
width: z.number().int().min(320).max(3840),
|
|
215
225
|
height: z.number().int().min(240).max(2160)
|
|
216
|
-
}).optional().describe('Varsayılan 1280x800 (deviceScaleFactor 1)'),
|
|
226
|
+
}).optional().describe('Varsayılan 1280x800 (deviceScaleFactor 1); mobil uygulamada varsayılan device. Verilirse device yok sayılır'),
|
|
227
|
+
device: z.union([
|
|
228
|
+
z.enum(['phone', 'tablet']),
|
|
229
|
+
z.object({
|
|
230
|
+
width: z.number().int().min(320).max(3840),
|
|
231
|
+
height: z.number().int().min(240).max(2160),
|
|
232
|
+
scale: z.number().min(1).max(3).optional()
|
|
233
|
+
})
|
|
234
|
+
]).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
235
|
fullPage: z.boolean().optional().describe('true: kaydırılan kısım dahil tüm sayfa; varsayılan yalnız viewport karesi'),
|
|
218
236
|
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
237
|
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."),
|
|
@@ -244,7 +262,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
244
262
|
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
263
|
},
|
|
246
264
|
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 }) => {
|
|
265
|
+
}, 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
266
|
try {
|
|
249
267
|
if (anonymous === true && (asUser !== undefined || cookies !== undefined)) {
|
|
250
268
|
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 +306,24 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
288
306
|
}
|
|
289
307
|
}
|
|
290
308
|
const url = `${config.studioBaseUrl}/${lane}/${projectSlug}/${applicationSlug}/${pageSlug}${languageChoice ? `?lang=${languageChoice.code}` : ''}`;
|
|
291
|
-
|
|
309
|
+
// Hedef ve cihaz uygulamanın platform profilinden (G3): mobil uygulama react-native-web
|
|
310
|
+
// çizimidir, varsayılan kare telefon ön ayarıdır. Tip okunamazsa web davranışı aynen.
|
|
311
|
+
const resolved = await resolveProfileBySlug(platform, projectSlug, applicationSlug);
|
|
312
|
+
const target = captureTargetFor(resolved.profile.capture.target);
|
|
313
|
+
const mobile = resolved.profile.platform === 'mobile';
|
|
314
|
+
if (device !== undefined && !mobile) {
|
|
315
|
+
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 }).`);
|
|
316
|
+
}
|
|
317
|
+
const deviceName = typeof device === 'string' ? device : device === undefined && mobile ? resolved.profile.capture.defaultDevice : undefined;
|
|
318
|
+
const preset = typeof device === 'object'
|
|
319
|
+
? { width: device.width, height: device.height, dpr: device.scale ?? 1 }
|
|
320
|
+
: deviceName === undefined ? undefined : resolved.profile.capture.devices[deviceName];
|
|
321
|
+
const appliedDevice = viewport === undefined && preset !== undefined ? preset : undefined;
|
|
322
|
+
const deviceNote = viewport !== undefined && device !== undefined
|
|
323
|
+
? 'viewport verildiği için device yok sayıldı: kare verilen viewport ve deviceScaleFactor 1 ile çekildi.'
|
|
324
|
+
: undefined;
|
|
325
|
+
const size = viewport ?? (appliedDevice ? { width: appliedDevice.width, height: appliedDevice.height } : { width: 1280, height: 800 });
|
|
326
|
+
const dpr = appliedDevice && appliedDevice.dpr !== 1 ? appliedDevice.dpr : undefined;
|
|
292
327
|
const extraCookies = Object.entries(cookies ?? {}).map(([name, value]) => ({ name, value }));
|
|
293
328
|
const identity = anonymous === true ? 'anonymous' : asUser === undefined ? 'gateway' : 'asUser';
|
|
294
329
|
const identityToken = anonymous ? undefined : resolveIdentity(asUser) ?? await platform.accessToken();
|
|
@@ -313,7 +348,9 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
313
348
|
...(quality === undefined ? {} : { quality }),
|
|
314
349
|
...(scale === undefined ? {} : { scale }),
|
|
315
350
|
...(clip === undefined ? {} : { clip: clip }),
|
|
316
|
-
...(actions === undefined ? {} : { actions: actions })
|
|
351
|
+
...(actions === undefined ? {} : { actions: actions }),
|
|
352
|
+
...(mobile ? { target } : {}),
|
|
353
|
+
...(dpr === undefined ? {} : { dpr })
|
|
317
354
|
});
|
|
318
355
|
// Fark ÖZETTEN çıkar; `summary:false` verilse de fark için toplanmıştır (yanıta girmez).
|
|
319
356
|
// Anahtar KİMLİĞİ ayırır (K8): 'asUser' etiketi yetmez — iki farklı üyenin jetonu aynı
|
|
@@ -384,6 +421,11 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
384
421
|
...(languageChoice?.note ? { languageNote: languageChoice.note } : {}),
|
|
385
422
|
...(colorScheme === undefined ? {} : { colorScheme }),
|
|
386
423
|
viewport: size,
|
|
424
|
+
...(appliedDevice === undefined ? {} : { device: typeof device === 'object' ? { ...device } : deviceName, deviceScaleFactor: appliedDevice.dpr }),
|
|
425
|
+
...(deviceNote === undefined ? {} : { deviceNote }),
|
|
426
|
+
// Web karesinin yanıtı profil öncesiyle aynı kalır (G3 kısıtı); alan yalnız mobilde.
|
|
427
|
+
...(mobile ? applicationTypeField(resolved) : {}),
|
|
428
|
+
...(target.platformNote === undefined ? {} : { platformNote: target.platformNote }),
|
|
387
429
|
fullPage: fullPage === true,
|
|
388
430
|
identity,
|
|
389
431
|
cookieNames,
|
|
@@ -535,19 +577,20 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
535
577
|
annotations: readOnly
|
|
536
578
|
}, handle(config, async ({ project, environment, applicationId, pageId, mode, componentId }) => {
|
|
537
579
|
const page = await fetchPage(platform, project, environment, applicationId, pageId);
|
|
580
|
+
const typeField = applicationTypeField(await resolveProfile(platform, applicationId));
|
|
538
581
|
const updatedAt = page.audit?.updatedAt ?? page.audit?.createdAt;
|
|
539
582
|
const version = page.version;
|
|
540
583
|
if (mode === 'full')
|
|
541
|
-
return { version, updatedAt, schema: page.schema ?? [] };
|
|
584
|
+
return { version, updatedAt, ...typeField, schema: page.schema ?? [] };
|
|
542
585
|
if (mode === 'subtree') {
|
|
543
586
|
if (!componentId)
|
|
544
587
|
throw new PlatformApiError("mode='subtree' için componentId zorunludur.");
|
|
545
588
|
const subtree = findNode(page.schema, componentId);
|
|
546
589
|
if (!subtree)
|
|
547
590
|
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 };
|
|
591
|
+
return { version, updatedAt, ...typeField, schema: subtree };
|
|
549
592
|
}
|
|
550
|
-
return { version, updatedAt, schema: (page.schema ?? []).map(node => summarize(node)) };
|
|
593
|
+
return { version, updatedAt, ...typeField, schema: (page.schema ?? []).map(node => summarize(node)) };
|
|
551
594
|
}));
|
|
552
595
|
server.registerTool('page_state', {
|
|
553
596
|
title: 'Sayfa durumunu getir',
|
|
@@ -573,15 +616,20 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
573
616
|
...schemaOpValidationErrors(opRecords).map(issue => `PAGE-016 — ${issue}`),
|
|
574
617
|
...codePropTemplateErrors(opRecords).map(issue => `PAGE-032 — ${issue}`)
|
|
575
618
|
];
|
|
576
|
-
const
|
|
619
|
+
const resolved = await resolveProfile(platform, applicationId);
|
|
620
|
+
const ordering = resolved.profile.layout.orderBy === 'block-position'
|
|
621
|
+
? await orderByIndex(opRecords, () => fetchPage(platform, project, environment, applicationId, pageId))
|
|
622
|
+
: { ops: opRecords, moved: [], warnings: [] };
|
|
623
|
+
const profileWarnings = await screenProfileWarnings(platform, resolved, project, environment, applicationId, { kind: 'page', ops: opRecords });
|
|
577
624
|
const response = ((await platform.tenant('POST', project, `/pages/${applicationId}/${pageId}/schema/validate`, {
|
|
578
625
|
environment,
|
|
579
626
|
body: { ops: [...allOps.filter(entry => entry.op === 'resize_component').flatMap(resizeServerOps), ...ordering.ops] }
|
|
580
627
|
})) ?? {});
|
|
581
628
|
if (validationIssues.length > 0)
|
|
582
629
|
response.validationWarnings = validationIssues;
|
|
583
|
-
if (ordering.warnings.length > 0)
|
|
584
|
-
response.layoutWarnings = ordering.warnings;
|
|
630
|
+
if (ordering.warnings.length > 0 || profileWarnings.length > 0)
|
|
631
|
+
response.layoutWarnings = [...ordering.warnings, ...profileWarnings];
|
|
632
|
+
Object.assign(response, applicationTypeField(resolved));
|
|
585
633
|
return withNormalization(response, [
|
|
586
634
|
...componentDefaultsNote(normalized.components),
|
|
587
635
|
...validationNote(normalized.validations),
|
|
@@ -642,13 +690,10 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
642
690
|
reject('PAGE-016', entry, schemaOpValidationErrors([entry]));
|
|
643
691
|
for (const entry of opRecords)
|
|
644
692
|
reject('PAGE-032', entry, codePropTemplateErrors([entry]));
|
|
645
|
-
if (opRecords.some(entry => entry.op === 'set_event'
|
|
693
|
+
if (opRecords.some(entry => entry.op === 'set_event')) {
|
|
646
694
|
const typesByElementId = new Map();
|
|
647
695
|
try {
|
|
648
|
-
|
|
649
|
-
collectTypes(page.schema, typesByElementId);
|
|
650
|
-
for (const entry of opRecords)
|
|
651
|
-
reject('PAGE-034', entry, boundEventOps(page.schema, [entry]));
|
|
696
|
+
collectTypes((await readPage()).schema, typesByElementId);
|
|
652
697
|
}
|
|
653
698
|
catch {
|
|
654
699
|
typesByElementId.clear();
|
|
@@ -660,7 +705,6 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
660
705
|
}
|
|
661
706
|
if (rejected.length > 0)
|
|
662
707
|
throw new PlatformApiError(rejectedOpsMessage(rejected), 400);
|
|
663
|
-
stripGatewayEventFields(opRecords);
|
|
664
708
|
const wantsRootAdd = opRecords.some(entry => entry.op === 'add_component' && (entry.parent === undefined || entry.parent === null || entry.parent === 'root'));
|
|
665
709
|
let retargeted = [];
|
|
666
710
|
if (wantsRootAdd) {
|
|
@@ -669,7 +713,12 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
669
713
|
retargeted = retargetRootAdds(opRecords, PAGE_CONTAINER_ELEMENT_ID);
|
|
670
714
|
}
|
|
671
715
|
}
|
|
672
|
-
const
|
|
716
|
+
const resolved = await resolveProfile(platform, applicationId);
|
|
717
|
+
const blocksMeaningful = resolved.profile.layout.blocksMeaningful;
|
|
718
|
+
// Mobil (dizi sırası): index platformun kendi add/move index'idir, konum çevirisi yapılmaz.
|
|
719
|
+
const ordering = resolved.profile.layout.orderBy === 'block-position'
|
|
720
|
+
? await orderByIndex(opRecords, readPage)
|
|
721
|
+
: { ops: opRecords, moved: [], warnings: [] };
|
|
673
722
|
const sentOps = ordering.ops;
|
|
674
723
|
// Akış silen op'lar yamadan ÖNCE hesaplanır: uyarı sonradan geldiğinde akış zaten kalıcı
|
|
675
724
|
// silinmiş oluyordu ve "önce yedekle" tavsiyesi uygulanamıyordu (09-05 koşusu, G-21).
|
|
@@ -729,10 +778,10 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
729
778
|
// görünür, iç içeliği ancak hedef parent zinciri belli olunca anlarsın.
|
|
730
779
|
nestedAfterOps = nestedRepeaterNodes(simulated);
|
|
731
780
|
layoutWarnings = [
|
|
732
|
-
...pageContainerPositionWarning(page.schema),
|
|
733
|
-
...verticalGapWarnings(simulated, 'PAGE-010 (uyarı)', touched),
|
|
781
|
+
...(blocksMeaningful ? pageContainerPositionWarning(page.schema) : []),
|
|
782
|
+
...(blocksMeaningful ? verticalGapWarnings(simulated, 'PAGE-010 (uyarı)', touched) : []),
|
|
734
783
|
...stringStyleWarning(stringStyleNodes(simulated)),
|
|
735
|
-
...inertFlexPropWarning(inertFlexPropNodes(simulated)),
|
|
784
|
+
...(resolved.profile.layout.modes.has('grid') ? inertFlexPropWarning(inertFlexPropNodes(simulated)) : []),
|
|
736
785
|
...flexSizeWarnings(simulated, 'PAGE-017 (uyarı)', 'PAGE-018 (uyarı)'),
|
|
737
786
|
...spacingLayerWarnings(simulated, 'PAGE-029 (uyarı)', 'PAGE-030 (uyarı)')
|
|
738
787
|
];
|
|
@@ -747,7 +796,12 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
747
796
|
const resized = resizeOps.length > 0 ? planResizes((await readPage()).schema ?? [], resizeOps) : { serverOps: [], applied: [], warnings: [] };
|
|
748
797
|
const propTypes = propTypeOps.length > 0 ? planPropTypes((await readPage()).schema ?? [], propTypeOps) : { serverOps: [], applied: [] };
|
|
749
798
|
const serverOps = [...removeEventOps, ...resized.serverOps, ...propTypes.serverOps, ...sentOps];
|
|
750
|
-
|
|
799
|
+
const profileWarnings = await screenProfileWarnings(platform, resolved, project, environment, applicationId, {
|
|
800
|
+
kind: 'page',
|
|
801
|
+
ops: [...opRecords, ...propTypeOps],
|
|
802
|
+
steps: await boundFlowSteps(platform, resolved, project, environment, 'page', applicationId, pageId, opRecords)
|
|
803
|
+
});
|
|
804
|
+
layoutWarnings = [...ordering.warnings, ...layoutWarnings, ...profileWarnings];
|
|
751
805
|
// Tek istek, tek sürüm: öykünülen op'lar 2026-09-14'e kadar ayrı GET + tam PUT çiftleriydi ve
|
|
752
806
|
// yamadan ÖNCE yazılıyordu — PATCH reddedilse bile kalıcıydılar. Artık hepsi aynı atomik PATCH'te.
|
|
753
807
|
// baseVersion yalnız biliniyorsa gider (ajanın verdiği ya da bu çağrıda zaten okunan sayfanınki):
|
|
@@ -791,16 +845,17 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
791
845
|
// alanın gerçekten KAYDEDİLDİĞİNİ söylemez. Yamadan sonra şemayı bir kez okuyup op'un yazmak
|
|
792
846
|
// istediğiyle kayıtlı olanı karşılaştırıyoruz. Okuma başarısız olursa yama yine geçerlidir.
|
|
793
847
|
const contentOps = sentOps.filter(entry => entry.op === 'add_component' || entry.op === 'replace_prop');
|
|
794
|
-
let opDrops =
|
|
848
|
+
let opDrops = [];
|
|
795
849
|
if (contentOps.length > 0) {
|
|
796
850
|
try {
|
|
797
851
|
const saved = await fetchPage(platform, project, environment, applicationId, pageId);
|
|
798
852
|
opDrops = schemaOpDrops(saved.schema, contentOps);
|
|
799
853
|
}
|
|
800
854
|
catch {
|
|
801
|
-
opDrops =
|
|
855
|
+
opDrops = [];
|
|
802
856
|
}
|
|
803
857
|
}
|
|
858
|
+
Object.assign(response, applicationTypeField(resolved));
|
|
804
859
|
return withWriteDiff(withNormalization(response, [
|
|
805
860
|
...rootAddsNote(retargeted, PAGE_CONTAINER_ELEMENT_ID),
|
|
806
861
|
...componentDefaultsNote(normalized.components),
|
|
@@ -826,7 +881,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
826
881
|
})));
|
|
827
882
|
server.registerTool('page_create', {
|
|
828
883
|
title: 'Sayfa oluştur',
|
|
829
|
-
description: "Yeni sayfa açar.
|
|
884
|
+
description: "Yeni sayfa açar. Şemayı burada göndermek yerine boş bırakıp page_schema_patch micro-op'larıyla kurmak daha doğrudur (canlı canvas ve hedefli düzeltme orada). Şemayı burada gönderirseniz 2026-09-08'den beri AYNI doğrulama duvarından geçer: tanınmayan prop adı ve geçersiz enum değeri 422 ile reddedilir ve sayfa HİÇ açılmaz. roleIds için role_list, layoutId için layout_list kullanın. Yanıttaki schema VARSAYILAN OLARAK iskelettir (çözülmüş layout düğümleri dâhil id/type/children'a iner) — tam gövde için view:'full'; gönderdiğiniz bir alan kaydedilmediyse silentlyDropped söyler.",
|
|
830
885
|
inputSchema: {
|
|
831
886
|
project: projectParam,
|
|
832
887
|
environment: environmentParam,
|
|
@@ -840,59 +895,25 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
840
895
|
middlewareIds: z.array(guid).optional(),
|
|
841
896
|
events: eventsParam.optional(),
|
|
842
897
|
schema: schemaParam.optional(),
|
|
843
|
-
view: writeViewParam
|
|
844
|
-
ifExists: ifExistsParam('sayfa', "mevcut sayfayı page_update gibi günceller — yalnız bu çağrıda VERİLEN alanlar yazılır, verilmeyen şema/olaylar/roller korunur (page_schema_patch ile kurulmuş şema betik tekrar koşunca silinmez); id'si ve flow'ları korunur")
|
|
898
|
+
view: writeViewParam
|
|
845
899
|
},
|
|
846
900
|
annotations: writes
|
|
847
|
-
}, handle(config, async ({ project, environment, applicationId, name, slug, browserTitle, browserTitleKey, layoutId, roleIds, middlewareIds, events, schema, view
|
|
848
|
-
const
|
|
901
|
+
}, handle(config, async ({ project, environment, applicationId, name, slug, browserTitle, browserTitleKey, layoutId, roleIds, middlewareIds, events, schema, view }) => {
|
|
902
|
+
const resolved = await resolveProfile(platform, applicationId);
|
|
903
|
+
const blocksMeaningful = resolved.profile.layout.blocksMeaningful;
|
|
904
|
+
const container = ensurePageContainer(schema ?? [], resolved.profile.layout.rootContainer);
|
|
849
905
|
assertScreenWriteGuards('page', { schema: container.schema, events });
|
|
850
906
|
const stringStyles = stringStyleNodes(container.schema);
|
|
851
907
|
const fixed = normalizeComponentDefaults(container.schema);
|
|
852
|
-
|
|
908
|
+
// Mobil çizer blokları okumaz (D3): kök konumu sıfırlama, blok sıralama ve dikey boşluk
|
|
909
|
+
// uyarısı yalnız ızgaranın anlamlı olduğu profilde koşar.
|
|
910
|
+
const reset = blocksMeaningful ? resetPageContainerPosition(container.schema) : [];
|
|
853
911
|
const catalogNotes = (schema ?? []).length > 0
|
|
854
912
|
? await screenCatalogNotes(platform, project, environment, applicationId, container.schema)
|
|
855
913
|
: [];
|
|
856
|
-
const moved = reorderSchemaBlocks(container.schema);
|
|
857
|
-
const gaps = (schema ?? []).length > 0 ? verticalGapWarnings(container.schema, 'PAGE-010 (uyarı)') : [];
|
|
858
|
-
const
|
|
859
|
-
code: 'PAGE-035',
|
|
860
|
-
noun: 'sayfa',
|
|
861
|
-
where: 'bu uygulamada',
|
|
862
|
-
name,
|
|
863
|
-
slug,
|
|
864
|
-
idParam: 'pageId',
|
|
865
|
-
ifExists,
|
|
866
|
-
list: () => listAll(platform, project, environment, `/pages/${applicationId}`),
|
|
867
|
-
listTool: 'page_list',
|
|
868
|
-
copyCheck: "kullanılıp kullanılmadığını dependency_graph module:'page' ile kontrol edip gerekirse silin",
|
|
869
|
-
updateTool: 'pageId ile page_update'
|
|
870
|
-
});
|
|
871
|
-
if (existing !== undefined) {
|
|
872
|
-
const pageId = String(existing.id);
|
|
873
|
-
const current = await fetchHost(platform, 'page', project, environment, applicationId, pageId);
|
|
874
|
-
const overrides = Object.fromEntries(Object.entries({
|
|
875
|
-
name, slug, browserTitle, browserTitleKey, layoutId, roleIds, middlewareIds, events,
|
|
876
|
-
schema: schema === undefined ? undefined : container.schema
|
|
877
|
-
}).filter(([, value]) => value !== undefined));
|
|
878
|
-
const body = pageUpdateBody(current, overrides);
|
|
879
|
-
const before = pageUpdateBody(current);
|
|
880
|
-
// Aynı ad/slug ile tekrar koşan betik o alanları "değişti" diye görmemeli (Gözlem 2).
|
|
881
|
-
const changed = Object.keys(overrides).filter(key => JSON.stringify(before[key]) !== JSON.stringify(overrides[key]));
|
|
882
|
-
await platform.tenant('PUT', project, `/pages/${applicationId}/${pageId}`, { environment, body });
|
|
883
|
-
const updateWarnings = schema === undefined
|
|
884
|
-
? []
|
|
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ı)')];
|
|
886
|
-
const summary = {
|
|
887
|
-
id: pageId,
|
|
888
|
-
outcome: 'updated',
|
|
889
|
-
changed,
|
|
890
|
-
preserved: { schemaNodes: countSchemaNodes(body.schema), events: body.events.length }
|
|
891
|
-
};
|
|
892
|
-
if (updateWarnings.length > 0)
|
|
893
|
-
summary.layoutWarnings = updateWarnings;
|
|
894
|
-
return withNormalization(summary, schema === undefined ? [] : [...container.fixes, ...reset, ...componentDefaultsNote(fixed), ...catalogNotes, ...reorderNote(moved)]);
|
|
895
|
-
}
|
|
914
|
+
const moved = blocksMeaningful ? reorderSchemaBlocks(container.schema) : [];
|
|
915
|
+
const gaps = (schema ?? []).length > 0 && blocksMeaningful ? verticalGapWarnings(container.schema, 'PAGE-010 (uyarı)') : [];
|
|
916
|
+
const profileWarnings = await screenProfileWarnings(platform, resolved, project, environment, applicationId, { kind: 'page', nodes: container.schema });
|
|
896
917
|
const response = await platform.tenant('POST', project, `/pages/${applicationId}`, {
|
|
897
918
|
environment,
|
|
898
919
|
body: {
|
|
@@ -914,16 +935,19 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
914
935
|
// kaydedildi mi" sorusunu gövde değil bu diff cevaplar. `layout_create`'in yaptığının sayfa
|
|
915
936
|
// ikizi; Prop sarmalı açılmazsa doğru kaydedilmiş her prop düşmüş görünür (G-28).
|
|
916
937
|
const drops = (schema ?? []).length > 0
|
|
917
|
-
?
|
|
918
|
-
:
|
|
938
|
+
? silentlyDropped({ schema: unwrapProps(container.schema) }, { schema: unwrapProps(saved.schema) })
|
|
939
|
+
: [];
|
|
919
940
|
if (view !== 'full' && Array.isArray(saved.schema)) {
|
|
920
941
|
saved.schema = saved.schema.map(node => summarize(node));
|
|
921
942
|
}
|
|
922
|
-
const normalized = withWriteDiff(withNormalization(
|
|
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
|
-
|
|
943
|
+
const normalized = withWriteDiff(withNormalization(response, [...container.fixes, ...reset, ...componentDefaultsNote(fixed), ...catalogNotes, ...reorderNote(moved)]), drops);
|
|
944
|
+
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];
|
|
945
|
+
const typeField = applicationTypeField(resolved);
|
|
946
|
+
if (!normalized || typeof normalized !== 'object')
|
|
925
947
|
return normalized;
|
|
926
|
-
|
|
948
|
+
if (createWarnings.length === 0)
|
|
949
|
+
return Object.keys(typeField).length === 0 ? normalized : { ...normalized, ...typeField };
|
|
950
|
+
return { ...normalized, ...typeField, layoutWarnings: createWarnings };
|
|
927
951
|
}));
|
|
928
952
|
server.registerTool('page_update', {
|
|
929
953
|
title: 'Sayfa üstbilgisini güncelle',
|
|
@@ -948,13 +972,21 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
948
972
|
const overrides = Object.fromEntries(Object.entries(changes).filter(([, value]) => value !== undefined));
|
|
949
973
|
assertScreenWriteGuards('page', { schema: overrides.schema, events: overrides.events });
|
|
950
974
|
const current = await fetchHost(platform, 'page', project, environment, applicationId, pageId);
|
|
975
|
+
const resolved = overrides.schema === undefined ? undefined : await resolveProfile(platform, applicationId);
|
|
976
|
+
const blocksMeaningful = resolved?.profile.layout.blocksMeaningful ?? true;
|
|
951
977
|
const fixed = normalizeComponentDefaults(overrides.schema);
|
|
952
|
-
const reset = overrides.schema === undefined ? [] : resetPageContainerPosition(overrides.schema);
|
|
978
|
+
const reset = overrides.schema === undefined || !blocksMeaningful ? [] : resetPageContainerPosition(overrides.schema);
|
|
953
979
|
const catalogNotes = await screenCatalogNotes(platform, project, environment, applicationId, overrides.schema);
|
|
954
|
-
const moved = overrides.schema === undefined ? [] : reorderSchemaBlocks(overrides.schema);
|
|
980
|
+
const moved = overrides.schema === undefined || !blocksMeaningful ? [] : reorderSchemaBlocks(overrides.schema);
|
|
955
981
|
const gaps = overrides.schema === undefined
|
|
956
982
|
? []
|
|
957
|
-
: [
|
|
983
|
+
: [
|
|
984
|
+
...(blocksMeaningful ? verticalGapWarnings(overrides.schema, 'PAGE-010 (uyarı)') : []),
|
|
985
|
+
...(resolved?.profile.layout.modes.has('grid') ?? true ? inertFlexPropWarning(inertFlexPropNodes(overrides.schema)) : []),
|
|
986
|
+
...flexSizeWarnings(overrides.schema, 'PAGE-017 (uyarı)', 'PAGE-018 (uyarı)'),
|
|
987
|
+
...spacingLayerWarnings(overrides.schema, 'PAGE-029 (uyarı)', 'PAGE-030 (uyarı)'),
|
|
988
|
+
...(resolved ? await screenProfileWarnings(platform, resolved, project, environment, applicationId, { kind: 'page', nodes: overrides.schema }) : [])
|
|
989
|
+
];
|
|
958
990
|
const body = pageUpdateBody(current, overrides);
|
|
959
991
|
await platform.tenant('PUT', project, `/pages/${applicationId}/${pageId}`, { environment, body });
|
|
960
992
|
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,
|
|
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
|
+
}
|
package/dist/tools/schemaOps.js
CHANGED
|
@@ -24,7 +24,7 @@ export function outlineWriteResponse(response) {
|
|
|
24
24
|
if (Array.isArray(container.appliedPatch))
|
|
25
25
|
container.appliedPatch = outlineAppliedPatch(container.appliedPatch);
|
|
26
26
|
}
|
|
27
|
-
export const opsParam = z.array(z.record(z.unknown())).describe("Micro-op listesi. Op sözlüğü: replace_prop {target, prop, value} (value ham değer, code sarmalı {type: 'code', ref: '{{ … }}'} ya da translation sarmalı {type: 'translation', ref: '<çeviri anahtarı>'} — prop tipini değiştirmek için komponenti silip yeniden EKLEMEYİN, ona yazan setter'ların hedefi silinir (FLOW-018); platform replace_prop'ta expression alanını OKUMAZ — gateway onu code sarmalına çevirir) | set_binding {target, prop, expression: '{{ Page.variables.x }}'} | add_component {parent (üst elementId ya da 'root'), index?, component} | remove_component {target} | move_component {target, parent, index?, position: {x, y}} (index ÇİZİM sırası değildir, runtime çocukları blocks.position'a göre (y, sonra x) çizer. page_schema_patch'te flex kapta gateway index'i görünen sıraya çevirir ve kardeş konumlarını yeniden yazar; ızgara kapta sırayı position.y verir, sona eklemeyen index PAGE-033 uyarısıdır. layout_schema_patch ve component_schema_patch bu çeviriyi YAPMAZ: orada sırayı position.y ile verin) | set_event {target, event, eventValue
|
|
27
|
+
export const opsParam = z.array(z.record(z.unknown())).describe("Micro-op listesi. Op sözlüğü: replace_prop {target, prop, value} (value ham değer, code sarmalı {type: 'code', ref: '{{ … }}'} ya da translation sarmalı {type: 'translation', ref: '<çeviri anahtarı>'} — prop tipini değiştirmek için komponenti silip yeniden EKLEMEYİN, ona yazan setter'ların hedefi silinir (FLOW-018); platform replace_prop'ta expression alanını OKUMAZ — gateway onu code sarmalına çevirir) | set_binding {target, prop, expression: '{{ Page.variables.x }}'} | add_component {parent (üst elementId ya da 'root'), index?, component} | remove_component {target} | move_component {target, parent, index?, position: {x, y}} (index ÇİZİM sırası değildir, runtime çocukları blocks.position'a göre (y, sonra x) çizer. page_schema_patch'te flex kapta gateway index'i görünen sıraya çevirir ve kardeş konumlarını yeniden yazar; ızgara kapta sırayı position.y verir, sona eklemeyen index PAGE-033 uyarısıdır. layout_schema_patch ve component_schema_patch bu çeviriyi YAPMAZ: orada sırayı position.y ile verin) | set_event {target, event, eventValue} | remove_event {target, event} (olayı diziden çıkarır; target: 'page' sayfa düzeyi olaylar. set_event ile eventValue: null olay SİLMEZ, değersiz ölü kayıt bırakır. Olmayan olay 422 missing-event) | set_validations {target, validations: [{type, message, value, isActive}]} (komponentin validation listesini TAMAMEN değiştirir; boş dizi hepsini siler) | set_appearance {target, appearance: {heightType?, widthType?, tooltip?, background?, …}} (kurulu düğümün appearance'ına YALNIZ verilen alanları yazar, gerisini korur; null alanı temizler; bilinmeyen alan unknown-appearance-field ile reddedilir. GÖRÜNÜRLÜK appearance'ta DEĞİLDİR: gizlemek için replace_prop {target, prop: 'isHidden', value: true}, koşul için set_binding {prop: 'isHidden'} — add_component gövdesindeki appearance.isHidden'ı gateway props.isHidden'a taşır) | resize_component {target, size?: {w,h}, appearance?: {widthType,widthString,heightType,heightString}, states?: {isWrapper,isScrollable,isSticky,isDroppable,isFloating,isQuickStack}, breakpoint?: 1024} (gateway adı: platforma set_block {target, breakpoint, size} + set_appearance {target, appearance} + set_states {target, states} olarak gider — o üç op'u doğrudan da gönderebilirsiniz. move_component'e konan size reddedilir. Izgarada blocks[].size, flex'te appearance geçerlidir; ebeveynin düzeni yazılanı etkisiz kılıyorsa PAGE-019 uyarısı çıkar. states bir PROP DEĞİL düğüm alanıdır ve replace_prop onu yazamaz — düğümü silmeden isWrapper bayrağını değiştirmenin yolu budur). set_prop_type {target, prop, propType: 'static'|'code'|'translation', ref?, data?} (replace_prop sarmalıyla aynı sonucu verir ve mevcut data'yı tasarım yedeği olarak taşır. Bu op prop'un TİPİNİ yerinde değiştirir. Taşıyıcı alan tipe göre: static → data, code ve translation → ref (runtime data'yı her turda ezer, anahtar yalnız ref'te yaşar). Eksik taşıyıcı istek gönderilmeden reddedilir; page_schema_validate aynı kararı verir) | reorder {parent ('root' ya da elementId), order: [çocuk elementId'lerinin TAMAMI, yeni sırada]} (yalnız DİZİ sırasını değiştirir; çizim sırası blocks.position'dır — görünen sırayı değiştirmek için konumları set_block/move_component ile de yazın) | set_state_binding {target, binding: {name, expression, targets}} (ada göre ekler/değiştirir) | remove_state_binding {target, name} | batch {gestureId?, label?, ops: [...]} (iç içe batch yok). target = elementId. İstek atomiktir: tek op reddedilirse hiçbiri yazılmaz.");
|
|
28
28
|
/**
|
|
29
29
|
* PAGE-008 — `set_event` uydurma bir olay adıyla 200 döner ve bağ SESSİZCE ölü kalır: motor olayı
|
|
30
30
|
* komponentin yaydığı adla eşleştirir, eşleşme yoksa akış hiç çalışmaz. Kontrol yalnız hedefin
|
|
@@ -35,7 +35,7 @@ export const opsParam = z.array(z.record(z.unknown())).describe("Micro-op listes
|
|
|
35
35
|
export function unknownEventOps(ops, typesByElementId, eventsBySlug) {
|
|
36
36
|
return ops
|
|
37
37
|
.map(op => op)
|
|
38
|
-
.filter(op => op.op === 'set_event'
|
|
38
|
+
.filter(op => op.op === 'set_event')
|
|
39
39
|
.flatMap(op => {
|
|
40
40
|
const target = String(op.target ?? '');
|
|
41
41
|
const event = String(op.event ?? '');
|
|
@@ -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
|
}
|
package/dist/tools/screens.js
CHANGED
|
@@ -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
|
-
|
|
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
|
}
|