@appaflytech/zeyrek-ai-mcp 0.0.23 → 0.0.24
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/.env.example +15 -0
- package/dist/config.js +9 -0
- package/dist/index.js +2 -0
- package/dist/knowledge/componentCatalog.js +1 -1
- package/dist/knowledge/mobileComponentCatalog.js +231 -58
- package/dist/knowledge/mobileComponentEvents.js +118 -3
- package/dist/knowledge/normalize.js +16 -6
- package/dist/knowledge/platform/lint.js +110 -3
- package/dist/knowledge/platform/mobileRules.js +64 -8
- package/dist/knowledge/rules.js +56 -7
- package/dist/knowledge/styleResolve.js +49 -3
- package/dist/knowledge/styleResolveNative.js +104 -4
- package/dist/observability/trace.js +1 -1
- package/dist/platform/argumentAliases.js +128 -0
- package/dist/platform/bodyFile.js +103 -0
- package/dist/platform/capture/capture.js +29 -5
- package/dist/platform/capture/console-log.js +80 -0
- package/dist/platform/capture/index.js +1 -0
- package/dist/platform/diagnostics.js +10 -4
- package/dist/platform/http.js +23 -7
- package/dist/platform/responses.js +6 -2
- package/dist/platform/schemaReadMemory.js +60 -0
- package/dist/platform/secrets.js +9 -0
- package/dist/platform/strictArguments.js +68 -12
- package/dist/platform/tokenVault.js +187 -4
- package/dist/resources/guides/expressions.md +1 -1
- package/dist/resources/guides/microservice.md +9 -2
- package/dist/resources/guides/middleware.md +5 -1
- package/dist/resources/guides/mobile.md +150 -14
- package/dist/resources/guides/page.md +146 -39
- package/dist/resources/guides/theme.md +4 -1
- package/dist/resources/guides/workflow.md +7 -1
- package/dist/resources/schema-fingerprint.json +2 -2
- package/dist/server.js +1 -1
- package/dist/tools/datasources.js +1 -1
- package/dist/tools/designSchemaPatch.js +4 -3
- package/dist/tools/externalComponents.js +1 -1
- package/dist/tools/flows.js +70 -4
- package/dist/tools/layouts.js +5 -5
- package/dist/tools/microservices.js +26 -10
- package/dist/tools/pages.js +192 -36
- package/dist/tools/platformInfo.js +4 -1
- package/dist/tools/rows.js +7 -1
- package/dist/tools/schemaOps.js +1 -1
- package/dist/tools/screenWrite.js +4 -3
- package/dist/tools/screens.js +81 -0
- package/package.json +1 -1
package/dist/tools/pages.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { guid, projectParam, environmentParam, pagingParams, readOnly, writes, updates, destructive, microOps, isBranchName } from './common.js';
|
|
2
2
|
import { orphanedFlowWarnings, orphanedFlowPlan, orphanedFlowMessage, boundEventOps, stripGatewayEventFields, 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
|
-
import { fetchHost, pageUpdateBody, schemaParam, eventsParam, unwrap, modeParam, summarize, findNode, flowPath } from './screens.js';
|
|
4
|
+
import { fetchHost, pageUpdateBody, schemaParam, eventsParam, unwrap, modeParam, summarize, findNode, flowPath, countSchemaNodes, missingElementIds, splitSchemaIntoParts, encodeSchemaCursor, decodeSchemaCursor } 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';
|
|
@@ -9,15 +9,16 @@ import { formBindingWarnings, formSubmitButtonWarnings } from '../knowledge/cove
|
|
|
9
9
|
import { emptyWriteDiff, schemaOpDrops, unwrapProps, withWriteDiff, writeDiff } from '../platform/writeDiff.js';
|
|
10
10
|
import { PlatformApiError } from '../platform/errors.js';
|
|
11
11
|
import { existingMatch, ifExistsParam, listAll, withOutcome } from '../platform/ifExists.js';
|
|
12
|
-
import { handle, toolResult, toolError } from '../platform/responses.js';
|
|
12
|
+
import { handle, toolResult, toolError, prune } from '../platform/responses.js';
|
|
13
13
|
import { StudioScreenshotter } from '../platform/capture/index.js';
|
|
14
14
|
import { captureTargetFor } from '../platform/capture/target.js';
|
|
15
15
|
import { diffSummaries, summaryLines, summaryStats } from '../platform/domSummary.js';
|
|
16
16
|
import { captureKey, imageHash, sharedCaptureMemory } from '../platform/screenshotMemory.js';
|
|
17
|
+
import { pageReadKey, schemaReadKey, sharedSchemaReadMemory } from '../platform/schemaReadMemory.js';
|
|
17
18
|
import { summarizeLayout } from '../knowledge/layoutSummary.js';
|
|
18
19
|
import { normalizeLanguageCode } from '../knowledge/languageCodes.js';
|
|
19
20
|
import { readProjectLanguages } from '../knowledge/languages.js';
|
|
20
|
-
import {
|
|
21
|
+
import { resolveIdentity } from '../platform/tokenVault.js';
|
|
21
22
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
22
23
|
import { dirname, resolve } from 'node:path';
|
|
23
24
|
import { setPropTypeErrors } from '../knowledge/propTypes.js';
|
|
@@ -60,10 +61,6 @@ async function orderByIndex(ops, readPage) {
|
|
|
60
61
|
return { ops, moved: [], warnings: [] };
|
|
61
62
|
}
|
|
62
63
|
}
|
|
63
|
-
/** Ş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. */
|
|
64
|
-
function countSchemaNodes(nodes) {
|
|
65
|
-
return (nodes ?? []).reduce((total, node) => total + 1 + countSchemaNodes(node.children), 0);
|
|
66
|
-
}
|
|
67
64
|
async function fetchPage(platform, project, environment, applicationId, pageId) {
|
|
68
65
|
const response = await platform.tenant('GET', project, `/pages/${applicationId}/${pageId}`, { environment });
|
|
69
66
|
return response?.data ?? {};
|
|
@@ -78,11 +75,41 @@ export const SERVER_PAGE_OPS = [
|
|
|
78
75
|
'replace_prop', 'set_binding', 'set_prop_type', 'add_component', 'remove_component', 'move_component', 'reorder', 'set_block',
|
|
79
76
|
'set_appearance', 'set_states', 'set_event', 'add_event', 'remove_event', 'set_validations', 'set_state_binding', 'remove_state_binding', 'batch'
|
|
80
77
|
];
|
|
81
|
-
export function registerPageTools(server, platform, injectedScreenshotter, injectedMemory) {
|
|
78
|
+
export function registerPageTools(server, platform, injectedScreenshotter, injectedMemory, injectedSchemaReadMemory) {
|
|
82
79
|
const config = platform.config;
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
const
|
|
80
|
+
const schemaReadMemory = injectedSchemaReadMemory ?? sharedSchemaReadMemory;
|
|
81
|
+
const readKey = (project, environment, pageId, version) => schemaReadKey(project ?? config.defaultProject ?? '', environment ?? config.defaultEnvironment, pageId, String(version ?? ''));
|
|
82
|
+
const pageKey = (project, environment, pageId) => pageReadKey(project ?? config.defaultProject ?? '', environment ?? config.defaultEnvironment, pageId);
|
|
83
|
+
const rememberVersion = (project, environment, pageId, version) => {
|
|
84
|
+
if ((typeof version === 'string' && version.length > 0) || typeof version === 'number') {
|
|
85
|
+
schemaReadMemory.rememberVersion(pageKey(project, environment, pageId), String(version));
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* g-05: sayfa sürüm çakışması (409 `PageSchemaVersionConflictException`) ajana "sayfa hangi
|
|
90
|
+
* sürümde, kim ne zaman değiştirdi" ile döner. Güncel sürüm sayfadan, son yazma revizyon
|
|
91
|
+
* defterinden (`/revisions/{projectId}`, en yeni tek satır) okunur; ikisi de okunamazsa mesaj
|
|
92
|
+
* o parçasız gider — çeviri hiçbir zaman çakışmanın kendisini gizlemez. Başka her hata olduğu
|
|
93
|
+
* gibi döner.
|
|
94
|
+
*/
|
|
95
|
+
const versionConflict = async (error, project, environment, applicationId, pageId, sentVersion, nextStep) => {
|
|
96
|
+
if (!(error instanceof PlatformApiError) || error.code !== 'PageSchemaVersionConflictException')
|
|
97
|
+
return error;
|
|
98
|
+
const currentVersion = await fetchPage(platform, project, environment, applicationId, pageId).then(page => page.version, () => undefined);
|
|
99
|
+
const revision = await (async () => {
|
|
100
|
+
const projectPayload = await platform.org('GET', `/p/${platform.resolveProject(project)}`);
|
|
101
|
+
const revisions = await platform.org('GET', `/revisions/${projectPayload?.data?.id ?? ''}`, {
|
|
102
|
+
query: { environment: environment ?? config.defaultEnvironment, module: 'page', recordId: pageId, pageLength: 1 }
|
|
103
|
+
});
|
|
104
|
+
return revisions?.data?.[0];
|
|
105
|
+
})().catch(() => undefined);
|
|
106
|
+
const actor = (revision?.actor ?? {});
|
|
107
|
+
const lastWrite = revision
|
|
108
|
+
? ` — son yazma ${String(revision.createdAt ?? '?')}, ${String(actor.name ?? actor.id ?? 'bilinmeyen kullanıcı')}, kaynak ${String(revision.source ?? '?')}, revizyon ${String(revision.revisionNo ?? '?')}`
|
|
109
|
+
: '';
|
|
110
|
+
return new PlatformApiError(`Sayfa siz okuduktan sonra değişti: gönderilen sürüm ${sentVersion ?? '(yok)'}, güncel sürüm ${String(currentVersion ?? '?')}${lastWrite}. HİÇBİR ŞEY yazılmadı. ${nextStep}`, 409, error.code);
|
|
111
|
+
};
|
|
112
|
+
const asUserParam = z.string().min(1).optional().describe("Uygulama üyesinin kimliği: istek gateway'in servis jetonu yerine bu kimlikle gider. Jeton HEM Authorization başlığında HEM NG_AccessToken çerezinde taşınır, böylece çerez okuyan middleware de geçer. ÜÇ ŞEKİL kabul edilir: (1) TERCİH EDİLEN `test:<ad>` — gateway kök .env'deki MCP_TEST_USER_<AD> satırının tarif ettiği giriş ucunu kendisi çağırır ve jetonu kasadan yeniden kullanır (satırı insan yazar; tanımsızsa hata eklenecek değişkeni söyler), (2) `tok_…` referansı — uygulamanın giriş ucunu microservice_execute ile çağırdığınızda jeton maskelenir ama yanıtın `tokenRefs` alanında referansı döner (ömür MCP_TOKEN_TTL_MINUTES; MCP_STATE_DIR varsa gateway yeniden başlayınca da çözülür), (3) ham JWT. anonymous ile birlikte verilemez.");
|
|
86
113
|
server.registerTool('page_list', {
|
|
87
114
|
title: 'Sayfaları listele',
|
|
88
115
|
description: 'Uygulamanın sayfalarını (id, name, slug) listeler; hedef sayfayı bulmak için ilk adım.',
|
|
@@ -104,13 +131,14 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
104
131
|
}));
|
|
105
132
|
server.registerTool('page_get', {
|
|
106
133
|
title: 'Sayfa üstbilgisini getir',
|
|
107
|
-
description: "Sayfanın kimlik ve bağ bilgisi: slug, browserTitle, layoutId, middlewareIds, roleIds, sayfa düzeyi olaylar (events[].value = flow id) ve flow özetleri. ŞEMA DÖNDÜRMEZ — şema için page_schema_get. 'Sayfa middleware'e bağlı mı, hangi roller görüyor, hangi olaya hangi akış bağlı' sorularının cevabı budur (Kural 8 denetimi).",
|
|
134
|
+
description: "Sayfanın kimlik ve bağ bilgisi: slug, browserTitle, layoutId, middlewareIds, roleIds, sayfa düzeyi olaylar (events[].value = flow id) ve flow özetleri. ŞEMA DÖNDÜRMEZ — şema için page_schema_get. 'Sayfa middleware'e bağlı mı, hangi roller görüyor, hangi olaya hangi akış bağlı' sorularının cevabı budur (Kural 8 denetimi). Yanıttaki version gateway'de hatırlanır: ardından gelen page_update onu baseVersion olarak gönderir (PAGE-039).",
|
|
108
135
|
inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid, pageId: guid },
|
|
109
136
|
annotations: readOnly
|
|
110
137
|
}, handle(config, async ({ project, environment, applicationId, pageId }) => {
|
|
111
138
|
const page = await fetchPage(platform, project, environment, applicationId, pageId);
|
|
112
139
|
const resolved = await resolveProfile(platform, applicationId);
|
|
113
140
|
const layout = (page.layout ?? {});
|
|
141
|
+
rememberVersion(project, environment, pageId, page.version);
|
|
114
142
|
return {
|
|
115
143
|
id: page.id,
|
|
116
144
|
name: page.name,
|
|
@@ -128,6 +156,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
128
156
|
flows: (Array.isArray(page.flows) ? page.flows : [])
|
|
129
157
|
.map(flow => ({ id: flow.id, name: flow.name, slug: flow.slug })),
|
|
130
158
|
schemaNodes: countSchemaNodes(page.schema),
|
|
159
|
+
version: page.version,
|
|
131
160
|
updatedAt: page.audit?.updatedAt ?? page.audit?.createdAt,
|
|
132
161
|
...applicationTypeField(resolved)
|
|
133
162
|
};
|
|
@@ -153,7 +182,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
153
182
|
}
|
|
154
183
|
// page_screenshot çerez, bu tool başlık taşıyordu; çerez okuyan middleware ikincisinden
|
|
155
184
|
// yapısal olarak geçemiyordu. Kimlik artık iki kanalda birden gider (Y-02).
|
|
156
|
-
const identity = asUser === undefined ? {} : { headers: { authorization: `Bearer ${resolveIdentity(asUser)}` } };
|
|
185
|
+
const identity = asUser === undefined ? {} : { headers: { authorization: `Bearer ${await resolveIdentity(asUser, platform, project, environment)}` } };
|
|
157
186
|
try {
|
|
158
187
|
response = await platform.tenant('GET', project, `/${applicationSlug}/p/${pageSlug}`, { environment, anonymous, mirrorAuthorizationCookie: true, ...identity });
|
|
159
188
|
}
|
|
@@ -218,7 +247,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
218
247
|
const maxInlinePngBytes = 5 * 1024 * 1024;
|
|
219
248
|
server.registerTool('page_screenshot', {
|
|
220
249
|
title: 'Sayfanın ekran görüntüsünü al',
|
|
221
|
-
description: "ÖNCE KARESİZ DENE: 'kart göründü mü, tablo doldu mu, hata var mı' gibi olgu sorularında image:'none' verin — kare çekilmez, yanıttaki summaryText her kök düğümün görünürlüğünü, metnini, satır sayısını, açık sekmesini ve hata mesajlarını söyler (bir tam sayfa karesinin yüzlerce katı küçük). Kareyi yalnız görsel soru (renk, hizalama, referansa sadakat) için alın ve küçültün: clip ile bölge, scale:0.5 ile yarı çözünürlük; aynı sayfayı yeniden çekerken image:'ifChanged'. Studio arayüzünün runtime render rotasını (/dev ya da /view) headless Chromium'da açar ve sayfanın PNG ekran görüntüsünü inline görüntü olarak döner — kurduğun sayfayı GÖZLE doğrulamanın yolu budur (page_runtime_get paketi verir, bu tool ekranda ne ÇİZİLDİĞİNİ gösterir). Sayfaya bağlı middleware'ler gerçekten koşar; reddedilirse yanıtta middlewareBlocked işaretlenir ve Studio'nun çizdiği hata durumu karesi döner — bu da bir doğrulamadır. YÜKLENMEMİŞ SAYFA ASLA ÇEKİLMEZ: kare, runtime çizilip mount akışları bitince alınır (yanıtta pageState + readyAfterMs); bütçe dolduğunda sayfa hâlâ iskelette ya da akış koşuyorsa çağrı ne görüldüğünü söyleyen bir hatayla döner, boş/yarım kare dönmez. Sayfa akışı bir adımda düşerse (patlayan ifade, hata dönen uç) yanıt runtimeErrors taşır ve hangi komponentin hangi prop'unun YAZILMADIĞINI söyler — kare 'çalışıyor' görünürken sessizce boş kalan komponenti ancak bu yakalar. 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).",
|
|
250
|
+
description: "ÖNCE KARESİZ DENE: 'kart göründü mü, tablo doldu mu, hata var mı' gibi olgu sorularında image:'none' verin — kare çekilmez, yanıttaki summaryText her kök düğümün görünürlüğünü, metnini, satır sayısını, açık sekmesini ve hata mesajlarını söyler (bir tam sayfa karesinin yüzlerce katı küçük). Kareyi yalnız görsel soru (renk, hizalama, referansa sadakat) için alın ve küçültün: clip ile bölge, scale:0.5 ile yarı çözünürlük; aynı sayfayı yeniden çekerken image:'ifChanged'. Studio arayüzünün runtime render rotasını (/dev ya da /view) headless Chromium'da açar ve sayfanın PNG ekran görüntüsünü inline görüntü olarak döner — kurduğun sayfayı GÖZLE doğrulamanın yolu budur (page_runtime_get paketi verir, bu tool ekranda ne ÇİZİLDİĞİNİ gösterir). Sayfaya bağlı middleware'ler gerçekten koşar; reddedilirse yanıtta middlewareBlocked işaretlenir ve Studio'nun çizdiği hata durumu karesi döner — bu da bir doğrulamadır. YÜKLENMEMİŞ SAYFA ASLA ÇEKİLMEZ: kare, runtime çizilip mount akışları bitince alınır (yanıtta pageState + readyAfterMs); bütçe dolduğunda sayfa hâlâ iskelette ya da akış koşuyorsa çağrı ne görüldüğünü söyleyen bir hatayla döner, boş/yarım kare dönmez. Sayfa akışı bir adımda düşerse (patlayan ifade, hata dönen uç) yanıt runtimeErrors taşır ve hangi komponentin hangi prop'unun YAZILMADIĞINI söyler — kare 'çalışıyor' görünürken sessizce boş kalan komponenti ancak bu yakalar. Sayfa ya da bir bileşen ÇİZİLEMEZSE neden yanıttadır: mobilde çöken düğüm renderErrors ({elementId, message}; sayfanın geri kalanı çizilir), yakalanmamış istisna pageErrors, konsol hatası consoleErrors — kare alınamadıysa bütün nedenler hata metninde. Kare 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).",
|
|
222
251
|
inputSchema: {
|
|
223
252
|
project: projectParam,
|
|
224
253
|
environment: environmentParam,
|
|
@@ -330,7 +359,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
330
359
|
const dpr = appliedDevice && appliedDevice.dpr !== 1 ? appliedDevice.dpr : undefined;
|
|
331
360
|
const extraCookies = Object.entries(cookies ?? {}).map(([name, value]) => ({ name, value }));
|
|
332
361
|
const identity = anonymous === true ? 'anonymous' : asUser === undefined ? 'gateway' : 'asUser';
|
|
333
|
-
const identityToken = anonymous ? undefined : resolveIdentity(asUser) ?? await platform.accessToken();
|
|
362
|
+
const identityToken = anonymous ? undefined : await resolveIdentity(asUser, platform, project, environment) ?? await platform.accessToken();
|
|
334
363
|
const cookieNames = [...new Set([...(identity === 'anonymous' ? [] : ['NG_AccessToken']), ...extraCookies.map(cookie => cookie.name)])];
|
|
335
364
|
const shot = await screenshotter.capture({
|
|
336
365
|
url,
|
|
@@ -520,6 +549,14 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
520
549
|
runtimeErrors: shot.runtimeErrors,
|
|
521
550
|
runtimeErrorsNote: `Sayfa akışı ${shot.runtimeErrors.length} yapısal hata bıraktı; kare 'çalışıyor' görünse bile bu adımlar işini YAPMADI. element/property alanı hangi komponentin hangi prop'unun yazılamadığını söyler — o komponent eski/boş değerindedir. Adımı flow_get ile açıp ifadeyi düzeltin.`
|
|
522
551
|
}),
|
|
552
|
+
...(shot.renderErrors === undefined
|
|
553
|
+
? {}
|
|
554
|
+
: {
|
|
555
|
+
renderErrors: shot.renderErrors,
|
|
556
|
+
renderErrorsNote: `${shot.renderErrors.length} düğüm çizilemedi: sayfanın geri kalanı çizildi, bu düğümlerin yerinde hata yer tutucusu var. message bileşen tipini ve hatayı söyler; genelde prop değeri ya da bileşenin kendisi (ARC kaydı).`
|
|
557
|
+
}),
|
|
558
|
+
...(shot.pageErrors === undefined ? {} : { pageErrors: shot.pageErrors }),
|
|
559
|
+
...(shot.consoleErrors === undefined ? {} : { consoleErrors: shot.consoleErrors }),
|
|
523
560
|
...(actionNotes.length === 0 ? {} : { actionsNote: actionNotes.join('\n') }),
|
|
524
561
|
...(shot.layout === undefined
|
|
525
562
|
? {}
|
|
@@ -569,30 +606,71 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
569
606
|
});
|
|
570
607
|
server.registerTool('page_schema_get', {
|
|
571
608
|
title: 'Sayfa şemasını oku',
|
|
572
|
-
description: "Sayfanın komponent şemasını okur. Yanıttaki version değerini page_schema_patch'e baseVersion olarak verin: sayfa o okumadan beri değiştiyse yama 409 ile reddedilir ve hiçbir op uygulanmaz.",
|
|
609
|
+
description: "Sayfanın komponent şemasını okur. Yanıttaki version değerini page_schema_patch'e baseVersion olarak verin: sayfa o okumadan beri değiştiyse yama 409 ile reddedilir ve hiçbir op uygulanmaz. full ve subtree yanıtı nodeCount taşır. full tek yanıta sığmazsa HATA döner (sessiz kırpma yok): cursor:'0' ile parça parça okuyun — her parça items[] ({ parentElementId, index, node }) taşır, kalem parentElementId düğümünün children[index]'inin yerine geçer (parentElementId yoksa kök dizinin [index]'i), alt ağacı sığmayan düğüm kabuk gelir (children: [{ elementId, deferred: true }]) ve sonraki parçalar onu doldurur; nextCursor bitene kadar sürün. allowTruncated:true kırpılmış gövdeyi açıkça verir (truncated: true; hangi düğümlerin düştüğü söylenmez) — o gövdeyi page_update'e geri yazmayın (PAGE-037).",
|
|
573
610
|
inputSchema: {
|
|
574
611
|
project: projectParam,
|
|
575
612
|
environment: environmentParam,
|
|
576
613
|
applicationId: guid,
|
|
577
614
|
pageId: guid,
|
|
578
|
-
mode: modeParam,
|
|
579
|
-
componentId: z.string().optional().describe("subtree kipinde kök alınacak komponentin elementId'si")
|
|
615
|
+
mode: modeParam.describe("summary (varsayılan): yalnız id/elementId/name/type ağacı — hedef bulmak için bunu kullanın. subtree: componentId ile tek alt ağaç. full: tüm şema; tek yanıta sığmazsa HATA döner — cursor:'0' ile parça parça okuyun (nextCursor bitene kadar) ya da allowTruncated:true ile kırpılmış gövdeyi açıkça isteyin."),
|
|
616
|
+
componentId: z.string().optional().describe("subtree kipinde kök alınacak komponentin elementId'si"),
|
|
617
|
+
cursor: z.string().min(1).optional().describe("Yalnız mode:'full': parçalı okuma. İlk çağrıda '0', sonra yanıttaki nextCursor; nextCursor gelmeyince şema bitmiştir. Sayfa okuma arasında değişirse 409 döner — cursor:'0' ile baştan başlayın."),
|
|
618
|
+
allowTruncated: z.boolean().optional().describe("Yalnız mode:'full': true verilirse sığmayan şema hata yerine kırpılarak döner (truncated: true, nodeCount toplamı söyler, düşen düğümler söylenmez). Varsayılan false: sığmayan şema hata verir.")
|
|
580
619
|
},
|
|
581
620
|
annotations: readOnly
|
|
582
|
-
}, handle(config, async ({ project, environment, applicationId, pageId, mode, componentId }) => {
|
|
621
|
+
}, handle(config, async ({ project, environment, applicationId, pageId, mode, componentId, cursor, allowTruncated }) => {
|
|
583
622
|
const page = await fetchPage(platform, project, environment, applicationId, pageId);
|
|
584
623
|
const typeField = applicationTypeField(await resolveProfile(platform, applicationId));
|
|
585
624
|
const updatedAt = page.audit?.updatedAt ?? page.audit?.createdAt;
|
|
586
625
|
const version = page.version;
|
|
587
|
-
|
|
588
|
-
|
|
626
|
+
rememberVersion(project, environment, pageId, version);
|
|
627
|
+
if (cursor !== undefined && mode !== 'full') {
|
|
628
|
+
throw new PlatformApiError(`cursor yalnız mode:'full' ile çalışır (verilen mode: '${mode ?? 'summary'}'); parçalı okuma tüm şema içindir. mode:'full' ile cursor:'0' verin ya da cursor'ı kaldırın.`, 400);
|
|
629
|
+
}
|
|
630
|
+
if (mode === 'full') {
|
|
631
|
+
const schema = page.schema ?? [];
|
|
632
|
+
const nodeCount = countSchemaNodes(schema);
|
|
633
|
+
const versionKey = String(version ?? '');
|
|
634
|
+
if (cursor !== undefined) {
|
|
635
|
+
const decoded = decodeSchemaCursor(cursor);
|
|
636
|
+
if (!decoded)
|
|
637
|
+
throw new PlatformApiError(`cursor '${cursor}' çözülemedi; cursor yalnız '0' ya da önceki yanıttaki nextCursor olabilir. cursor:'0' ile baştan başlayın.`, 400);
|
|
638
|
+
if (decoded.version !== undefined && decoded.version !== versionKey) {
|
|
639
|
+
throw new PlatformApiError(`Sayfa bu cursor alındıktan sonra değişti (cursor sürümü '${decoded.version}', güncel sürüm '${versionKey}'); eski parçalarla yenileri birleştirilirse şema tutarsız olur. Toplanan parçaları atın ve cursor:'0' ile baştan okuyun.`, 409);
|
|
640
|
+
}
|
|
641
|
+
const parts = splitSchemaIntoParts(schema, Math.floor(config.maxResponseBytes * 0.75));
|
|
642
|
+
const items = parts[decoded.partIndex];
|
|
643
|
+
if (!items)
|
|
644
|
+
throw new PlatformApiError(`cursor '${cursor}' ${decoded.partIndex}. parçayı istiyor ama şema ${parts.length} parça; son parçadan sonra nextCursor gelmez. cursor:'0' ile baştan başlayın.`, 400);
|
|
645
|
+
const nextIndex = decoded.partIndex + 1;
|
|
646
|
+
return {
|
|
647
|
+
version,
|
|
648
|
+
updatedAt,
|
|
649
|
+
...typeField,
|
|
650
|
+
nodeCount,
|
|
651
|
+
part: { index: decoded.partIndex, count: parts.length, nodes: countSchemaNodes(items.map(item => item.node)) },
|
|
652
|
+
items,
|
|
653
|
+
...(nextIndex < parts.length ? { nextCursor: encodeSchemaCursor(versionKey, nextIndex) } : {})
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
const full = { version, updatedAt, ...typeField, nodeCount, schema };
|
|
657
|
+
const chars = (JSON.stringify(prune(full)) ?? '').length;
|
|
658
|
+
if (chars > config.maxResponseBytes) {
|
|
659
|
+
if (allowTruncated !== true) {
|
|
660
|
+
const kb = (value) => (value / 1024).toFixed(1);
|
|
661
|
+
throw new PlatformApiError(`Şema ${nodeCount} düğüm / ${kb(chars)} KB; tek yanıta sığmaz (bütçe ${kb(config.maxResponseBytes)} KB). Kırpılmış şema hangi düğümlerin düştüğünü söylemez ve geri yazılırsa sayfa eksilir. cursor:'0' ile parça parça okuyun ya da allowTruncated:true verin (kırpılır, hangi düğümlerin düştüğü söylenmez). Hedef bulmak için mode:'summary' yeter.`, 400);
|
|
662
|
+
}
|
|
663
|
+
schemaReadMemory.markTruncated(readKey(project, environment, pageId, version));
|
|
664
|
+
}
|
|
665
|
+
return full;
|
|
666
|
+
}
|
|
589
667
|
if (mode === 'subtree') {
|
|
590
668
|
if (!componentId)
|
|
591
669
|
throw new PlatformApiError("mode='subtree' için componentId zorunludur.");
|
|
592
670
|
const subtree = findNode(page.schema, componentId);
|
|
593
671
|
if (!subtree)
|
|
594
672
|
throw new PlatformApiError(`Sayfada '${componentId}' id'li bir komponent yok; page_schema_get'i summary kipinde çağırıp hedefi doğrulayın.`);
|
|
595
|
-
return { version, updatedAt, ...typeField, schema: subtree };
|
|
673
|
+
return { version, updatedAt, ...typeField, nodeCount: countSchemaNodes([subtree]), schema: subtree };
|
|
596
674
|
}
|
|
597
675
|
return { version, updatedAt, ...typeField, schema: (page.schema ?? []).map(node => summarize(node)) };
|
|
598
676
|
}));
|
|
@@ -624,7 +702,12 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
624
702
|
const ordering = resolved.profile.layout.orderBy === 'block-position'
|
|
625
703
|
? await orderByIndex(opRecords, () => fetchPage(platform, project, environment, applicationId, pageId))
|
|
626
704
|
: { ops: opRecords, moved: [], warnings: [] };
|
|
627
|
-
const
|
|
705
|
+
const writesStyle = resolved.profile.platform !== 'web' && opRecords.some(entry => (entry.op === 'replace_prop' || entry.op === 'set_binding') && entry.prop === 'style');
|
|
706
|
+
const profileWarnings = await screenProfileWarnings(platform, resolved, project, environment, applicationId, {
|
|
707
|
+
kind: 'page',
|
|
708
|
+
ops: opRecords,
|
|
709
|
+
savedNodes: writesStyle ? await fetchPage(platform, project, environment, applicationId, pageId).then(page => page.schema, () => undefined) : undefined
|
|
710
|
+
});
|
|
628
711
|
const response = ((await platform.tenant('POST', project, `/pages/${applicationId}/${pageId}/schema/validate`, {
|
|
629
712
|
environment,
|
|
630
713
|
body: { ops: [...allOps.filter(entry => entry.op === 'resize_component').flatMap(resizeServerOps), ...ordering.ops] }
|
|
@@ -748,7 +831,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
748
831
|
const added = opRecords
|
|
749
832
|
.filter(entry => entry.op === 'add_component')
|
|
750
833
|
.map(entry => entry.component);
|
|
751
|
-
assertScreenWriteGuards('page', { schema: added });
|
|
834
|
+
assertScreenWriteGuards('page', { schema: added }, resolved.profile.platform);
|
|
752
835
|
const catalogNotes = await screenCatalogNotes(platform, project, environment, applicationId, added);
|
|
753
836
|
const formWarnings = [...formBindingWarnings(opRecords), ...formSubmitButtonWarnings(opRecords)];
|
|
754
837
|
let flowWarnings = [];
|
|
@@ -799,14 +882,16 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
799
882
|
}
|
|
800
883
|
}
|
|
801
884
|
if (nestedAfterOps.length > 0)
|
|
802
|
-
throw new PlatformApiError(nestedRepeaterMessage(nestedAfterOps), 400);
|
|
885
|
+
throw new PlatformApiError(nestedRepeaterMessage(nestedAfterOps, resolved.profile.platform), 400);
|
|
803
886
|
const removedEvents = planEventRemovals(removeEventOps);
|
|
804
887
|
const resized = resizeOps.length > 0 ? planResizes((await readPage()).schema ?? [], resizeOps) : { serverOps: [], applied: [], warnings: [] };
|
|
805
888
|
const propTypes = propTypeOps.length > 0 ? planPropTypes((await readPage()).schema ?? [], propTypeOps) : { serverOps: [], applied: [] };
|
|
806
889
|
const serverOps = [...removeEventOps, ...resized.serverOps, ...propTypes.serverOps, ...sentOps];
|
|
890
|
+
const writesStyle = resolved.profile.platform !== 'web' && opRecords.some(entry => (entry.op === 'replace_prop' || entry.op === 'set_binding') && entry.prop === 'style');
|
|
807
891
|
const profileWarnings = await screenProfileWarnings(platform, resolved, project, environment, applicationId, {
|
|
808
892
|
kind: 'page',
|
|
809
893
|
ops: [...opRecords, ...propTypeOps],
|
|
894
|
+
savedNodes: writesStyle ? await readPage().then(page => page.schema, () => undefined) : undefined,
|
|
810
895
|
steps: await boundFlowSteps(platform, resolved, project, environment, 'page', applicationId, pageId, opRecords)
|
|
811
896
|
});
|
|
812
897
|
layoutWarnings = [...ordering.warnings, ...layoutWarnings, ...profileWarnings];
|
|
@@ -814,12 +899,34 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
814
899
|
// yamadan ÖNCE yazılıyordu — PATCH reddedilse bile kalıcıydılar. Artık hepsi aynı atomik PATCH'te.
|
|
815
900
|
// baseVersion yalnız biliniyorsa gider (ajanın verdiği ya da bu çağrıda zaten okunan sayfanınki):
|
|
816
901
|
// sürümü öğrenmek için ayrı GET atılmaz, platform satırı her yamada kilitler.
|
|
902
|
+
const sentVersion = baseVersion ?? (typeof cachedPage?.version === 'string' ? cachedPage.version : undefined);
|
|
817
903
|
const response = ((await platform.tenant('PATCH', project, `/pages/${applicationId}/${pageId}/schema`, {
|
|
818
904
|
environment,
|
|
819
|
-
body: { ops: serverOps, baseVersion:
|
|
905
|
+
body: { ops: serverOps, baseVersion: sentVersion }
|
|
906
|
+
}).catch(async (error) => {
|
|
907
|
+
throw await versionConflict(error, project, environment, applicationId, pageId, sentVersion, "page_schema_get ile yeniden okuyup planı tazeleyin ve yeni version'ı baseVersion olarak gönderin; aynı op'ları eski sürümle tekrar göndermek yine 409 alır.");
|
|
820
908
|
})) ?? {});
|
|
909
|
+
const patched = (response.data && typeof response.data === 'object' && !Array.isArray(response.data) ? response.data : response);
|
|
910
|
+
const unchanged = Array.isArray(patched.unchanged) ? patched.unchanged.map(String) : [];
|
|
911
|
+
rememberVersion(project, environment, pageId, patched.newVersion);
|
|
912
|
+
// g-05: platform (b-05) aynı yere aynı içerikle zaten duran add_component'i etkisiz sayar; bütün
|
|
913
|
+
// op'lar etkisizse yazmaz ve eski baseVersion'a 409 demez. Ajan bunu "hata yok ama bir şey de
|
|
914
|
+
// olmadı" diye okumasın: yanıtı kaybolan çağrının tekrarı olduğu açıkça söylenir.
|
|
915
|
+
if (unchanged.length > 0) {
|
|
916
|
+
response.alreadyApplied = patched.noop !== true
|
|
917
|
+
? `Şu op'lar sayfada zaten duruyordu, yeniden yazılmadı (tekrar zararsız): ${unchanged.join(', ')}. Diğer op'lar uygulandı.`
|
|
918
|
+
: patched.stale === true
|
|
919
|
+
? `Bu yamanın bütün op'ları sayfada zaten duruyor (${unchanged.join(', ')}) — hiçbir şey yazılmadı. AMA sayfa gönderdiğiniz baseVersion'dan beri başka bir yazımla da değişti: newVersion sizin gönderdiğiniz sürümdür, güncel sürüm değil. Devam etmeden önce page_schema_get ile güncel hâli okuyun; eski sürümle gelen sonraki yazma 409 alır.`
|
|
920
|
+
: `Bu yamanın bütün op'ları sayfada zaten duruyor (${unchanged.join(', ')}): büyük olasılıkla önceki çağrı uygulandı ama yanıtı size ulaşmadı. Tekrar zararsızdır — hiçbir şey yazılmadı, sürüm artmadı; newVersion sayfanın güncel sürümüdür.`;
|
|
921
|
+
}
|
|
821
922
|
if (view !== 'full')
|
|
822
923
|
outlineWriteResponse(response);
|
|
924
|
+
if (baseVersion && schemaReadMemory.wasTruncated(readKey(project, environment, pageId, baseVersion))) {
|
|
925
|
+
response.warnings = [
|
|
926
|
+
...(Array.isArray(response.warnings) ? response.warnings : []),
|
|
927
|
+
`PAGE-038 (uyarı) — baseVersion '${baseVersion}' bu gateway'de page_schema_get allowTruncated:true ile KIRPIK okundu; plan düşen düğümleri görmeden yapılmış olabilir. Micro-op veri düşürmez ve yama uygulandı, ama hedef ve konumları page_schema_get mode:'summary' ya da cursor:'0' ile tam okuyup doğrulayın.`
|
|
928
|
+
];
|
|
929
|
+
}
|
|
823
930
|
if (removedEvents.length > 0)
|
|
824
931
|
response.removedEvents = removedEvents;
|
|
825
932
|
if (propTypes.applied.length > 0)
|
|
@@ -904,14 +1011,14 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
904
1011
|
events: eventsParam.optional(),
|
|
905
1012
|
schema: schemaParam.optional(),
|
|
906
1013
|
view: writeViewParam,
|
|
907
|
-
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")
|
|
1014
|
+
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. Şema verilirse sayfanın şeması BİLEREK tam yazılır (page_update force:true gibi — gateway sayfayı okuduğu anın sürümüyle yazar, aradaki Studio değişikliği ezilir); hedefli değişiklik için page_schema_patch")
|
|
908
1015
|
},
|
|
909
1016
|
annotations: writes
|
|
910
1017
|
}, handle(config, async ({ project, environment, applicationId, name, slug, browserTitle, browserTitleKey, layoutId, roleIds, middlewareIds, events, schema, view, ifExists }) => {
|
|
911
1018
|
const resolved = await resolveProfile(platform, applicationId);
|
|
912
1019
|
const blocksMeaningful = resolved.profile.layout.blocksMeaningful;
|
|
913
1020
|
const container = ensurePageContainer(schema ?? [], resolved.profile.layout.rootContainer);
|
|
914
|
-
assertScreenWriteGuards('page', { schema: container.schema, events });
|
|
1021
|
+
assertScreenWriteGuards('page', { schema: container.schema, events }, resolved.profile.platform);
|
|
915
1022
|
const stringStyles = stringStyleNodes(container.schema);
|
|
916
1023
|
const fixed = normalizeComponentDefaults(container.schema);
|
|
917
1024
|
// Mobil çizer blokları okumaz (D3): kök konumu sıfırlama, blok sıralama ve dikey boşluk
|
|
@@ -947,7 +1054,8 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
947
1054
|
const before = pageUpdateBody(current);
|
|
948
1055
|
// Aynı ad/slug ile tekrar koşan betik o alanları "değişti" diye görmemeli (Gözlem 2).
|
|
949
1056
|
const changed = Object.keys(overrides).filter(key => JSON.stringify(before[key]) !== JSON.stringify(overrides[key]));
|
|
950
|
-
await platform.tenant('PUT', project, `/pages/${applicationId}/${pageId}`, { environment, body });
|
|
1057
|
+
const written = await platform.tenant('PUT', project, `/pages/${applicationId}/${pageId}`, { environment, body });
|
|
1058
|
+
rememberVersion(project, environment, pageId, written?.data?.version);
|
|
951
1059
|
const updateWarnings = schema === undefined
|
|
952
1060
|
? []
|
|
953
1061
|
: [...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];
|
|
@@ -979,6 +1087,8 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
979
1087
|
const saved = (response && typeof response === 'object' && !Array.isArray(response)
|
|
980
1088
|
? (response.data ?? response)
|
|
981
1089
|
: {});
|
|
1090
|
+
if (typeof saved.id === 'string')
|
|
1091
|
+
rememberVersion(project, environment, saved.id, saved.version);
|
|
982
1092
|
// Kırpmadan ÖNCE doğrulama yolu (B-55): yanıt gövdesi iskelete inince "gönderdiğim prop
|
|
983
1093
|
// kaydedildi mi" sorusunu gövde değil bu diff cevaplar. `layout_create`'in yaptığının sayfa
|
|
984
1094
|
// ikizi; Prop sarmalı açılmazsa doğru kaydedilmiş her prop düşmüş görünür (G-28).
|
|
@@ -999,7 +1109,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
999
1109
|
}));
|
|
1000
1110
|
server.registerTool('page_update', {
|
|
1001
1111
|
title: 'Sayfa üstbilgisini güncelle',
|
|
1002
|
-
description: "Sayfanın adını/slug'ını/layout'unu/rollerini değiştirir. Platformun PUT'u REPLACE'tir; gateway sayfayı önce okur, yalnız verdiğiniz alanları değiştirip tam gövdeyi geri yazar — bu yüzden şema ve olaylar kaybolmaz. ŞEMA değişikliği için bu tool'u değil page_schema_patch'i kullanın (doğrulama duvarı + canlı canvas orada).",
|
|
1112
|
+
description: "Sayfanın adını/slug'ını/layout'unu/rollerini değiştirir. Platformun PUT'u REPLACE'tir; gateway sayfayı önce okur, yalnız verdiğiniz alanları değiştirip tam gövdeyi geri yazar — bu yüzden şema ve olaylar kaybolmaz. ŞEMA değişikliği için bu tool'u değil page_schema_patch'i kullanın (doğrulama duvarı + canlı canvas orada). SÜRÜMSÜZ YAZMAZ (PAGE-039): baseVersion verilmezse bu gateway'in o sayfada en son gördüğü sürüm gider (page_get, page_schema_get, page_schema_patch'in newVersion'ı ya da önceki page_update); hiçbiri yoksa istek gönderilmez — önce okuyun. Sayfa o sürümden beri değiştiyse (Studio'da açık sekme, başka ajan) 409 döner, kim ve ne zaman değiştirdiği söylenir ve hiçbir şey yazılmaz. force:true son sürümü okuyup onun üstüne bilerek yazar (yanıt forced: true).",
|
|
1003
1113
|
inputSchema: {
|
|
1004
1114
|
project: projectParam,
|
|
1005
1115
|
environment: environmentParam,
|
|
@@ -1013,14 +1123,33 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
1013
1123
|
roleIds: z.array(guid).optional(),
|
|
1014
1124
|
middlewareIds: z.array(guid).optional(),
|
|
1015
1125
|
events: eventsParam.optional(),
|
|
1016
|
-
schema: schemaParam.optional().describe('Nadiren gerekir; verilmezse mevcut şema korunur')
|
|
1126
|
+
schema: schemaParam.optional().describe('Nadiren gerekir; verilmezse mevcut şema korunur. Tam REPLACE: kayıtlı sayfadan daha az düğüm taşıyan şema allowNodeLoss olmadan reddedilir (PAGE-037)'),
|
|
1127
|
+
allowNodeLoss: z.boolean().optional().describe('true: gönderilen schema kayıtlı sayfadan daha az düğüm taşısa da yazılır — eksik düğümler geri dönüşsüz silinir. Varsayılan false: PAGE-037 ile reddedilir (kırpık okumanın geri yazılması)'),
|
|
1128
|
+
baseVersion: z.string().optional().describe("Değişikliği planladığınız sayfa sürümü (page_get / page_schema_get yanıtındaki version). Verilmezse bu gateway'in o sayfada en son gördüğü sürüm gider; sayfa o sürümden beri değiştiyse 409 döner ve hiçbir şey yazılmaz"),
|
|
1129
|
+
force: z.boolean().optional().describe("true: sürüm kontrolünü bilerek geçer — gateway sayfanın son sürümünü okuyup onun üstüne yazar, aradaki değişiklik (Studio sekmesi, başka ajan) ezilir. Yanıt forced: true taşır. Varsayılan false")
|
|
1017
1130
|
},
|
|
1018
1131
|
annotations: updates
|
|
1019
|
-
}, handle(config, async ({ project, environment, applicationId, pageId, ...changes }) => {
|
|
1132
|
+
}, handle(config, async ({ project, environment, applicationId, pageId, allowNodeLoss, baseVersion, force, ...changes }) => {
|
|
1133
|
+
const knownVersion = baseVersion ?? schemaReadMemory.lastVersion(pageKey(project, environment, pageId));
|
|
1134
|
+
if (knownVersion === undefined && force !== true) {
|
|
1135
|
+
throw new PlatformApiError("PAGE-039 — page_update hangi sürümün üstüne yazdığını bilmiyor: baseVersion verilmedi ve bu gateway sayfayı henüz okumadı. Platformun PUT'u REPLACE'tir; sürümsüz tam gövde Studio'da açık bir sekmenin ya da başka bir ajanın değişikliğini sessizce ezer (Bitki Bakım koşusu, 14:02). Önce page_get ya da page_schema_get ile okuyun — yanıttaki version gateway'de hatırlanır — ya da onu baseVersion olarak verin. Son sürümün üstüne bilerek yazmak için force:true. İstek gönderilmedi.", 400);
|
|
1136
|
+
}
|
|
1020
1137
|
const overrides = Object.fromEntries(Object.entries(changes).filter(([, value]) => value !== undefined));
|
|
1021
|
-
assertScreenWriteGuards('page', { schema: overrides.schema, events: overrides.events });
|
|
1022
|
-
const current = await fetchHost(platform, 'page', project, environment, applicationId, pageId);
|
|
1023
1138
|
const resolved = overrides.schema === undefined ? undefined : await resolveProfile(platform, applicationId);
|
|
1139
|
+
assertScreenWriteGuards('page', { schema: overrides.schema, events: overrides.events }, resolved?.profile.platform);
|
|
1140
|
+
const current = await fetchHost(platform, 'page', project, environment, applicationId, pageId);
|
|
1141
|
+
if (overrides.schema !== undefined && allowNodeLoss !== true) {
|
|
1142
|
+
const storedSchema = current.schema;
|
|
1143
|
+
const sentSchema = overrides.schema;
|
|
1144
|
+
const stored = countSchemaNodes(storedSchema);
|
|
1145
|
+
const sent = countSchemaNodes(sentSchema);
|
|
1146
|
+
if (sent < stored) {
|
|
1147
|
+
const missing = missingElementIds(storedSchema, sentSchema);
|
|
1148
|
+
const listed = missing.slice(0, 10).join(', ');
|
|
1149
|
+
const more = missing.length > 10 ? ` (+${missing.length - 10} daha)` : '';
|
|
1150
|
+
throw new PlatformApiError(`PAGE-037 — gönderilen şema ${sent} düğüm, kayıtlı sayfa ${stored} düğüm; eksilen elementId'ler: ${listed}${more}. Platformun PUT'u REPLACE'tir: eksik düğümler geri dönüşsüz silinir — kırpık (allowTruncated) ya da yarım bir okuma geri yazılıyor olabilir, istek gönderilmedi. Şema değişikliğini page_schema_patch micro-op'larıyla yapın (silmek için remove_component); düğümleri bilerek siliyorsanız allowNodeLoss:true verin.`, 400);
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1024
1153
|
const blocksMeaningful = resolved?.profile.layout.blocksMeaningful ?? true;
|
|
1025
1154
|
const fixed = normalizeComponentDefaults(overrides.schema);
|
|
1026
1155
|
const reset = overrides.schema === undefined || !blocksMeaningful ? [] : resetPageContainerPosition(overrides.schema);
|
|
@@ -1036,12 +1165,19 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
1036
1165
|
...(resolved ? await screenProfileWarnings(platform, resolved, project, environment, applicationId, { kind: 'page', nodes: overrides.schema }) : [])
|
|
1037
1166
|
];
|
|
1038
1167
|
const body = pageUpdateBody(current, overrides);
|
|
1039
|
-
|
|
1168
|
+
if (force !== true)
|
|
1169
|
+
body.baseVersion = knownVersion;
|
|
1170
|
+
const written = await platform.tenant('PUT', project, `/pages/${applicationId}/${pageId}`, { environment, body }).catch(async (error) => {
|
|
1171
|
+
throw await versionConflict(error, project, environment, applicationId, pageId, String(body.baseVersion ?? ''), "page_schema_get ile yeniden okuyun ve şema değişikliğini page_schema_patch ile uygulayın — yalnız dokunduğunuz yer yazılır, aradaki değişiklik korunur. Üstbilgi (ad, slug, layout, roller) için okuduktan sonra page_update'i yeniden çağırın; aradaki değişikliği bilerek ezmek için force:true.");
|
|
1172
|
+
});
|
|
1173
|
+
rememberVersion(project, environment, pageId, written?.data?.version);
|
|
1040
1174
|
const summary = {
|
|
1041
1175
|
id: pageId,
|
|
1042
1176
|
changed: Object.keys(overrides),
|
|
1043
|
-
preserved: { schemaNodes: body.schema
|
|
1177
|
+
preserved: { schemaNodes: countSchemaNodes(body.schema), events: body.events.length }
|
|
1044
1178
|
};
|
|
1179
|
+
if (force === true)
|
|
1180
|
+
summary.forced = true;
|
|
1045
1181
|
if (gaps.length > 0)
|
|
1046
1182
|
summary.layoutWarnings = gaps;
|
|
1047
1183
|
return withNormalization(summary, [...reset, ...componentDefaultsNote(fixed), ...catalogNotes, ...reorderNote(moved)]);
|
|
@@ -1052,6 +1188,26 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
1052
1188
|
inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid, pageId: guid },
|
|
1053
1189
|
annotations: writes
|
|
1054
1190
|
}, handle(config, ({ project, environment, applicationId, pageId }) => platform.tenant('POST', project, `/pages/${applicationId}/clone/${pageId}`, { environment })));
|
|
1191
|
+
server.registerTool('page_restore', {
|
|
1192
|
+
title: 'Sayfayı bir revizyona döndür',
|
|
1193
|
+
description: "Sayfayı revizyon defterindeki N. sürümüne döndürür: şema (komponentler eski id'leriyle), sayfa olayları, roller, middleware'ler ve layout o sürümdeki gibi yazılır; o sürümün bağladığı ama bugün silinmiş akışlar da kendi son revizyonlarından AYNI id ile geri kurulur (önce adımsız akış, sonra şema, sonra adımlar — tek transaction). Boşalan ya da bayat bir kaydın ezdiği sayfayı (ZM-27) böyle geri alın; tek akış için flow_restore. N'yi revision_list(module: 'page', recordId: <pageId>) ile bulun, içeriğini revision_get/revision_diff ile doğrulayın. N'den SONRA bağlanan akışlar yetim kalır ve silinir — yanıt deletedFlows ile listeler, flow_restore ile geri gelir. baseVersion (page_schema_get version) verilirse sayfa o sürümden beri değiştiyse 409. N'nin bağladığı bir akışın revizyonu kalmamışsa 409 ve hiçbir şey yazılmaz (hata akış id'lerini söyler). DİKKAT: şemayı yüklemeyen komutların (UpdateProjectCommand, UpdateApplicationCommand…) yazdığı sayfa revizyonları komponent TAŞIMAZ; komponenti olan bir sayfayı böyle bir revizyona döndürmek platformca 409 ile reddedilir — correlationId'si sayfa kaydı olan (PatchPageSchemaCommand, UpdatePageCommand) revizyonu seçin. Sayfayı bilerek boşaltmak için allowEmpty: true.",
|
|
1194
|
+
inputSchema: {
|
|
1195
|
+
project: projectParam,
|
|
1196
|
+
environment: environmentParam,
|
|
1197
|
+
applicationId: guid,
|
|
1198
|
+
pageId: guid,
|
|
1199
|
+
revisionNo: z.number().int().min(0).describe("Dönülecek sayfa revizyonunun numarası (revision_list revisionNo)"),
|
|
1200
|
+
baseVersion: z.string().min(1).optional().describe('page_schema_get çıktısındaki version; sayfa o sürümden beri değiştiyse 409 döner ve hiçbir şey yazılmaz'),
|
|
1201
|
+
allowEmpty: z.boolean().optional().describe('true: komponentsiz bir revizyona dönüp sayfayı bilerek boşalt. Varsayılan false — platform komponenti olan sayfayı boş revizyona döndürmeyi 409 ile reddeder')
|
|
1202
|
+
},
|
|
1203
|
+
annotations: destructive
|
|
1204
|
+
}, handle(config, async ({ project, environment, applicationId, pageId, revisionNo, baseVersion, allowEmpty }) => {
|
|
1205
|
+
const restored = unwrap(await platform.tenant('POST', project, `/pages/${applicationId}/${pageId}/restore`, {
|
|
1206
|
+
environment,
|
|
1207
|
+
body: { revisionNo, ...(baseVersion ? { baseVersion } : {}), ...(allowEmpty ? { allowEmpty: true } : {}) }
|
|
1208
|
+
}));
|
|
1209
|
+
return { pageId, ...restored };
|
|
1210
|
+
}));
|
|
1055
1211
|
server.registerTool('page_delete', {
|
|
1056
1212
|
title: 'Sayfa sil',
|
|
1057
1213
|
description: 'Sayfayı siler. Ana sayfa (isHomepage) silinemez; önce başka bir sayfayı ana sayfa yapın. Silmeden önce dependency_graph module:\'page\' ile bağımlılıkları kontrol edin.',
|
|
@@ -93,6 +93,7 @@ export function registerPlatformTools(server, platform) {
|
|
|
93
93
|
'Modülün iş kuralları rehberini döner. Bir modülde İLK yazma işleminden önce ilgili rehberi okuyun:',
|
|
94
94
|
...guides.map(name => `- ${name}${guideRouting[name] ? `: ${guideRouting[name]}` : ''}`),
|
|
95
95
|
"Büyük rehberler (page ~120 KB) section'sız çağrıda TAM METİN DÖNMEZ: giriş + bölüm listesi (outline) döner, truncated: true olur. Gövdeyi section ile bölüm bölüm alın.",
|
|
96
|
+
"Büyük gövdeyi (sayfa şeması, akış adımları, uç/workflow tanımı, patch op'ları) dosyaya yazıp bodyFile ile verin: gövde bağlamınızdan geçmez, satır içiyle aynı doğrulamadan geçer (page_*/flow_*/microservice_*/workflow_* create/update/patch). microservice_execute yanıt başlıkları varsayılan kısadır; ucun kendi başlığı için includeHeaders:'all'.",
|
|
96
97
|
'Örnek gövdeler için ngapps://examples kaynaklarına bakın.'
|
|
97
98
|
].join('\n');
|
|
98
99
|
server.registerTool('platform_whoami', {
|
|
@@ -155,6 +156,7 @@ export function registerPlatformTools(server, platform) {
|
|
|
155
156
|
title: 'Kabiliyet boşluğu bildir',
|
|
156
157
|
description: "Yapmak İSTEDİĞİNİZ ama platform desteklemediği için yapamadığınız her noktada BUNU ÇAĞIRIN — tasarımı sessizce küçültmeyin. Bu çağrı platforma gitmez, yalnızca günlüğe 'KABİLİYET BOŞLUĞU' bloğu yazar; hata günlüğünde görünmeyen bu kayıtlar ürün yol haritasının girdisidir. Önce rehbere bakıp (platform_guide; hazır komponent yetmiyorsa tek seferlik için 'html' kaçış kapısı, kalıcı bir ihtiyaç içinse 'external-component' — kendi komponentinizi yazıp yayınlayabilirsiniz) gerçekten desteklenmediğinden emin olun: desteklenen bir şeyi 'yok' diye bildirmek yanlış yol haritası üretir.",
|
|
157
158
|
inputSchema: {
|
|
159
|
+
project: z.string().min(1).optional().describe("Boşluğun görüldüğü proje (ad ya da slug); kayda yazılır. Bu çağrı platforma gitmez, proje yalnız bağlamdır"),
|
|
158
160
|
goal: z.string().min(1).optional().describe('Ne yapmak istediniz — kullanıcıya görünen sonuç olarak yazın ("randevu listesini hasta adına göre süzmek"). Eşanlamlı kabul edilir: title · summary · what.'),
|
|
159
161
|
blocker: z.string().min(1).optional().describe('Neden yapamadınız — hangi kabiliyetin eksik olduğu ("uçta X yok", "komponentte Y prop\'u yok"). Eşanlamlı kabul edilir: reason · problem · missing · description.'),
|
|
160
162
|
severity: z.string().optional().describe("YOL HARİTASINDAKİ SIRAYI BU ALAN BELİRLER, doldurun. blocker: özellik hiç yapılamadı · workaround: daha kötü bir yolla yapıldı · cosmetic: yalnız görsel/ergonomik kayıp. Eşanlamlılar çevrilir (critical/high/blocking→blocker, medium/major→workaround, low/minor/nit→cosmetic). TANINMAYAN değer reddedilir; verilmezse kayıt düşer ama yanıt 'incomplete' ile uyarır ve bildirim orta kovaya yazılır."),
|
|
@@ -172,9 +174,10 @@ export function registerPlatformTools(server, platform) {
|
|
|
172
174
|
annotations: readOnly
|
|
173
175
|
}, handle(config, async (input) => {
|
|
174
176
|
const gap = normalizeGapReport(input);
|
|
175
|
-
logCapabilityGap({ goal: gap.goal, blocker: gap.blocker, severity: gap.severity, module: gap.module, attempted: gap.attempted, workaround: gap.workaround });
|
|
177
|
+
logCapabilityGap({ goal: gap.goal, blocker: gap.blocker, severity: gap.severity, module: gap.module, attempted: gap.attempted, workaround: gap.workaround, project: input.project });
|
|
176
178
|
return {
|
|
177
179
|
recorded: true,
|
|
180
|
+
...(input.project ? { project: input.project } : {}),
|
|
178
181
|
severity: gap.severity,
|
|
179
182
|
module: gap.module ?? null,
|
|
180
183
|
...(Object.keys(gap.normalizedFrom).length > 0 ? { normalizedFrom: gap.normalizedFrom } : {}),
|
package/dist/tools/rows.js
CHANGED
|
@@ -4,7 +4,7 @@ import { unmappedMembers, withUnmappedMembers } from '../knowledge/schemaGuard.j
|
|
|
4
4
|
import { withNormalization } from '../knowledge/normalize.js';
|
|
5
5
|
import { RefResolver } from '../platform/refs.js';
|
|
6
6
|
import { attachFindings, evaluatePolicy, enforcePolicy } from '../knowledge/policy.js';
|
|
7
|
-
import { policyRules } from '../knowledge/rules.js';
|
|
7
|
+
import { filterOperatorError, policyRules } from '../knowledge/rules.js';
|
|
8
8
|
import { languageHeader } from '../knowledge/languageCodes.js';
|
|
9
9
|
import { handle } from '../platform/responses.js';
|
|
10
10
|
import { uploadFile } from './files.js';
|
|
@@ -224,6 +224,9 @@ export function registerRowTools(server, platform) {
|
|
|
224
224
|
},
|
|
225
225
|
annotations: readOnly
|
|
226
226
|
}, handle(config, async ({ project, environment, entityId, language, ...body }) => {
|
|
227
|
+
const operatorError = filterOperatorError('row_list', body.filters);
|
|
228
|
+
if (operatorError)
|
|
229
|
+
throw new PlatformApiError(operatorError, 400);
|
|
227
230
|
const notes = [];
|
|
228
231
|
const headers = languageHeader(language, notes);
|
|
229
232
|
const resolver = new RefResolver(platform, project, environment);
|
|
@@ -400,6 +403,9 @@ export function registerRowTools(server, platform) {
|
|
|
400
403
|
if ((scope?.rowIds?.length ?? 0) > maxSelectedRowIds) {
|
|
401
404
|
throw new PlatformApiError(`row_export: ${scope?.rowIds?.length} satır id'si, ${maxSelectedRowIds} sınırının üstünde. Bu kadar satırı seçerek değil filtreyle verin: scope { kind: 'view', filters: [...] }.`);
|
|
402
405
|
}
|
|
406
|
+
const operatorError = filterOperatorError('row_export', scope?.filters);
|
|
407
|
+
if (operatorError)
|
|
408
|
+
throw new PlatformApiError(operatorError, 400);
|
|
403
409
|
const resolvedFormat = format ?? 'xlsx';
|
|
404
410
|
const notes = [];
|
|
405
411
|
const resolver = new RefResolver(platform, project, environment);
|
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, replaceBoundFlows?} (olayın BÜTÜN bağlarını bu tek akışa indirir; olayda başka akış bağlıysa gateway PAGE-034 ile reddeder — bilerek değiştirmek için replaceBoundFlows: true) | add_event {target, event, eventValue} (olaya bir akış DAHA bağlar; bağlı akışların hepsi paralel ve birbirinden bağımsız koşar, sıra garantisi yoktur — sıra gerekiyorsa flow-trigger. Aynı
|
|
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, replaceBoundFlows?} (olayın BÜTÜN bağlarını bu tek akışa indirir; olayda başka akış bağlıysa gateway PAGE-034 ile reddeder — bilerek değiştirmek için replaceBoundFlows: true) | add_event {target, event, eventValue} (olaya bir akış DAHA bağlar; bağlı akışların hepsi paralel ve birbirinden bağımsız koşar, sıra garantisi yoktur — sıra gerekiyorsa flow-trigger. Aynı akışı ikinci kez bağlamak etkisizdir (unchanged; tekrar güvenli). Mobil runtime yalnız İLK akışı koşar) | remove_event {target, event, eventValue?} (olayı diziden çıkarır; target: 'page' sayfa düzeyi olaylar. Olaya birden fazla akış bağlıysa eventValue ile hangisi olduğunu verin, yoksa 422 ambiguous-event. 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,9 +35,10 @@ export function invalidComponentNames(nodes) {
|
|
|
35
35
|
* PAGE-009 ve PAGE-013'ün layout ayağı 2026-09-09'da eklendi: ikisi de yalnız sayfa yollarında
|
|
36
36
|
* koşuyordu, oysa layout `views`'ü aynı runtime'dan geçer — bozuk ad o layout'a bağlı HER
|
|
37
37
|
* sayfanın ifadelerini öldürür, iç içe repeater'ın geri yazma kırılması ise yapısaldır ve
|
|
38
|
-
* barındıranın sayfa mı layout mu olduğuna bakmaz.
|
|
38
|
+
* barındıranın sayfa mı layout mu olduğuna bakmaz. `platform` yalnız PAGE-013 mesajının önerisini seçer
|
|
39
|
+
* (mobilde `grouped-list`); denetimlerin kendisi iki platformda aynıdır.
|
|
39
40
|
*/
|
|
40
|
-
export function assertScreenWriteGuards(kind, screen) {
|
|
41
|
+
export function assertScreenWriteGuards(kind, screen, platform) {
|
|
41
42
|
const codes = GUARD_CODES[kind];
|
|
42
43
|
const badEvents = invalidScreenEventNames(screen.events, codes.events);
|
|
43
44
|
if (badEvents.length > 0)
|
|
@@ -54,7 +55,7 @@ export function assertScreenWriteGuards(kind, screen) {
|
|
|
54
55
|
}
|
|
55
56
|
const nested = nestedRepeaterNodes(screen.schema);
|
|
56
57
|
if (nested.length > 0)
|
|
57
|
-
throw new PlatformApiError(nestedRepeaterMessage(nested), 400);
|
|
58
|
+
throw new PlatformApiError(nestedRepeaterMessage(nested, platform), 400);
|
|
58
59
|
}
|
|
59
60
|
/**
|
|
60
61
|
* Katalog kurallarını (blok sınırı · auto-height · boyut string'i) ağaca uygular ve
|