@appaflytech/zeyrek-ai-mcp 0.0.24 → 0.0.26
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 +0 -15
- package/dist/config.js +0 -11
- package/dist/index.js +0 -2
- package/dist/knowledge/componentCatalog.js +25 -13
- package/dist/knowledge/componentEvents.js +6 -13
- package/dist/knowledge/normalize.js +17 -68
- package/dist/knowledge/rules.js +49 -137
- package/dist/knowledge/styleResolve.js +4 -50
- package/dist/knowledge/themeResolve.js +3 -3
- package/dist/observability/trace.js +1 -1
- package/dist/platform/diagnostics.js +4 -10
- package/dist/platform/domSummary.js +1 -1
- package/dist/platform/http.js +7 -23
- package/dist/platform/responses.js +2 -6
- package/dist/platform/{capture/capture.js → screenshot.js} +514 -52
- package/dist/platform/secrets.js +0 -9
- package/dist/platform/strictArguments.js +12 -68
- package/dist/platform/tokenVault.js +4 -187
- package/dist/resources/examples/workflow-http-step.json +2 -2
- package/dist/resources/guides/component.md +1 -6
- package/dist/resources/guides/expressions.md +2 -2
- package/dist/resources/guides/functions.md +1 -1
- package/dist/resources/guides/layout.md +7 -11
- package/dist/resources/guides/microfunction.md +28 -0
- package/dist/resources/guides/microservice.md +4 -10
- package/dist/resources/guides/middleware.md +1 -5
- package/dist/resources/guides/page.md +55 -215
- package/dist/resources/guides/theme.md +1 -4
- package/dist/resources/guides/translation.md +1 -1
- package/dist/resources/guides/workflow.md +9 -12
- package/dist/resources/schema-fingerprint.json +2 -2
- package/dist/resources/schemas/CreateWorkflowCommand.json +3 -0
- package/dist/resources/schemas/PreviewWorkflowCommand.json +3 -0
- package/dist/resources/schemas/StepDto.json +12 -9
- package/dist/resources/schemas/UpdateWorkflowCommand.json +3 -0
- package/dist/server.js +1 -3
- package/dist/tools/applications.js +3 -60
- package/dist/tools/components.js +10 -50
- package/dist/tools/datasources.js +1 -1
- package/dist/tools/designSchemaPatch.js +6 -16
- package/dist/tools/externalComponents.js +4 -25
- package/dist/tools/flows.js +6 -79
- package/dist/tools/layouts.js +12 -44
- package/dist/tools/microservices.js +10 -26
- package/dist/tools/pages.js +67 -303
- package/dist/tools/platformInfo.js +2 -6
- package/dist/tools/rows.js +1 -7
- package/dist/tools/schemaOps.js +1 -1
- package/dist/tools/screenWrite.js +5 -6
- package/dist/tools/screens.js +3 -90
- package/dist/tools/workflows.js +1 -1
- package/package.json +1 -1
- package/dist/knowledge/mobileComponentCatalog.js +0 -801
- package/dist/knowledge/mobileComponentEvents.js +0 -225
- package/dist/knowledge/platform/lint.js +0 -312
- package/dist/knowledge/platform/mobile.js +0 -78
- package/dist/knowledge/platform/mobileRules.js +0 -182
- package/dist/knowledge/platform/mobileTabs.js +0 -108
- package/dist/knowledge/platform/profile.js +0 -20
- package/dist/knowledge/platform/resolve.js +0 -122
- package/dist/knowledge/platform/web.js +0 -38
- package/dist/knowledge/styleResolveNative.js +0 -510
- package/dist/platform/argumentAliases.js +0 -128
- package/dist/platform/bodyFile.js +0 -103
- package/dist/platform/capture/actions.js +0 -333
- package/dist/platform/capture/browser.js +0 -1
- package/dist/platform/capture/console-log.js +0 -80
- package/dist/platform/capture/index.js +0 -11
- package/dist/platform/capture/readiness.js +0 -103
- package/dist/platform/capture/summary.js +0 -77
- package/dist/platform/capture/target.js +0 -5
- package/dist/platform/capture/targets/studio-mobile.js +0 -15
- package/dist/platform/capture/targets/studio-web.js +0 -5
- package/dist/platform/schemaReadMemory.js +0 -60
- package/dist/resources/examples/mobile-app-shell-drawer.json +0 -209
- package/dist/resources/examples/mobile-app-shell-stack.json +0 -123
- package/dist/resources/examples/mobile-app-shell-tab.json +0 -174
- package/dist/resources/examples/mobile-list-detail-form.json +0 -656
- package/dist/resources/guides/mobile.md +0 -419
- package/dist/tools/mobile.js +0 -203
- package/dist/tools/profileWrite.js +0 -45
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
|
|
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';
|
|
@@ -9,24 +9,19 @@ 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
|
|
13
|
-
import { StudioScreenshotter } from '../platform/
|
|
14
|
-
import { captureTargetFor } from '../platform/capture/target.js';
|
|
12
|
+
import { handle, toolResult, toolError } from '../platform/responses.js';
|
|
13
|
+
import { StudioScreenshotter } from '../platform/screenshot.js';
|
|
15
14
|
import { diffSummaries, summaryLines, summaryStats } from '../platform/domSummary.js';
|
|
16
15
|
import { captureKey, imageHash, sharedCaptureMemory } from '../platform/screenshotMemory.js';
|
|
17
|
-
import { pageReadKey, schemaReadKey, sharedSchemaReadMemory } from '../platform/schemaReadMemory.js';
|
|
18
16
|
import { summarizeLayout } from '../knowledge/layoutSummary.js';
|
|
19
17
|
import { normalizeLanguageCode } from '../knowledge/languageCodes.js';
|
|
20
18
|
import { readProjectLanguages } from '../knowledge/languages.js';
|
|
21
|
-
import {
|
|
19
|
+
import { resolveIdentityToken } from '../platform/tokenVault.js';
|
|
22
20
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
23
21
|
import { dirname, resolve } from 'node:path';
|
|
24
22
|
import { setPropTypeErrors } from '../knowledge/propTypes.js';
|
|
25
23
|
import { designSnapshotInput } from '../knowledge/designSnapshot.js';
|
|
26
24
|
import { discoverPage } from '../knowledge/pageDiscover.js';
|
|
27
|
-
import { applicationTypeField, resolveProfile, resolveProfileBySlug } from '../knowledge/platform/resolve.js';
|
|
28
|
-
import { resolveThemeNative } from '../knowledge/styleResolveNative.js';
|
|
29
|
-
import { boundFlowSteps, screenProfileWarnings } from './profileWrite.js';
|
|
30
25
|
import { z } from 'zod';
|
|
31
26
|
/**
|
|
32
27
|
* Runtime paketindeki `appFunctions`/`pageFunctions` (UiFunctionRuntimeDto: id, name, parameters,
|
|
@@ -61,6 +56,10 @@ async function orderByIndex(ops, readPage) {
|
|
|
61
56
|
return { ops, moved: [], warnings: [] };
|
|
62
57
|
}
|
|
63
58
|
}
|
|
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
|
+
}
|
|
64
63
|
async function fetchPage(platform, project, environment, applicationId, pageId) {
|
|
65
64
|
const response = await platform.tenant('GET', project, `/pages/${applicationId}/${pageId}`, { environment });
|
|
66
65
|
return response?.data ?? {};
|
|
@@ -75,41 +74,11 @@ export const SERVER_PAGE_OPS = [
|
|
|
75
74
|
'replace_prop', 'set_binding', 'set_prop_type', 'add_component', 'remove_component', 'move_component', 'reorder', 'set_block',
|
|
76
75
|
'set_appearance', 'set_states', 'set_event', 'add_event', 'remove_event', 'set_validations', 'set_state_binding', 'remove_state_binding', 'batch'
|
|
77
76
|
];
|
|
78
|
-
export function registerPageTools(server, platform, injectedScreenshotter, injectedMemory
|
|
77
|
+
export function registerPageTools(server, platform, injectedScreenshotter, injectedMemory) {
|
|
79
78
|
const config = platform.config;
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
const
|
|
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.");
|
|
79
|
+
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. İKİ ŞEKİL kabul edilir: (1) ham JWT, (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; jetonu hiç görmeden onu buraya verin (15 dk geçerli). anonymous ile birlikte verilemez.");
|
|
80
|
+
/** Sayfa ve uç araçları aynı kimlik sözleşmesini paylaşır (G-20); çözüm tokenVault'ta yaşar. */
|
|
81
|
+
const resolveIdentity = resolveIdentityToken;
|
|
113
82
|
server.registerTool('page_list', {
|
|
114
83
|
title: 'Sayfaları listele',
|
|
115
84
|
description: 'Uygulamanın sayfalarını (id, name, slug) listeler; hedef sayfayı bulmak için ilk adım.',
|
|
@@ -131,14 +100,12 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
131
100
|
}));
|
|
132
101
|
server.registerTool('page_get', {
|
|
133
102
|
title: 'Sayfa üstbilgisini getir',
|
|
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).
|
|
103
|
+
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).",
|
|
135
104
|
inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid, pageId: guid },
|
|
136
105
|
annotations: readOnly
|
|
137
106
|
}, handle(config, async ({ project, environment, applicationId, pageId }) => {
|
|
138
107
|
const page = await fetchPage(platform, project, environment, applicationId, pageId);
|
|
139
|
-
const resolved = await resolveProfile(platform, applicationId);
|
|
140
108
|
const layout = (page.layout ?? {});
|
|
141
|
-
rememberVersion(project, environment, pageId, page.version);
|
|
142
109
|
return {
|
|
143
110
|
id: page.id,
|
|
144
111
|
name: page.name,
|
|
@@ -156,9 +123,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
156
123
|
flows: (Array.isArray(page.flows) ? page.flows : [])
|
|
157
124
|
.map(flow => ({ id: flow.id, name: flow.name, slug: flow.slug })),
|
|
158
125
|
schemaNodes: countSchemaNodes(page.schema),
|
|
159
|
-
|
|
160
|
-
updatedAt: page.audit?.updatedAt ?? page.audit?.createdAt,
|
|
161
|
-
...applicationTypeField(resolved)
|
|
126
|
+
updatedAt: page.audit?.updatedAt ?? page.audit?.createdAt
|
|
162
127
|
};
|
|
163
128
|
}));
|
|
164
129
|
server.registerTool('page_runtime_get', {
|
|
@@ -182,7 +147,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
182
147
|
}
|
|
183
148
|
// page_screenshot çerez, bu tool başlık taşıyordu; çerez okuyan middleware ikincisinden
|
|
184
149
|
// yapısal olarak geçemiyordu. Kimlik artık iki kanalda birden gider (Y-02).
|
|
185
|
-
const identity = asUser === undefined ? {} : { headers: { authorization: `Bearer ${
|
|
150
|
+
const identity = asUser === undefined ? {} : { headers: { authorization: `Bearer ${resolveIdentity(asUser)}` } };
|
|
186
151
|
try {
|
|
187
152
|
response = await platform.tenant('GET', project, `/${applicationSlug}/p/${pageSlug}`, { environment, anonymous, mirrorAuthorizationCookie: true, ...identity });
|
|
188
153
|
}
|
|
@@ -206,17 +171,11 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
206
171
|
const styleSource = resolveStyles ? (theme ?? null) : undefined;
|
|
207
172
|
const asNodes = (value) => (Array.isArray(value) ? value : []);
|
|
208
173
|
const named = (value) => (Array.isArray(value) ? value : []).map(entry => ({ id: entry.id, name: entry.name, slug: entry.slug }));
|
|
209
|
-
const runtimeApplication = (body.application ?? {});
|
|
210
|
-
// Mobil uygulama React Native stiliyle çizilir (G4): rapor CSS kaskadı değil `shell`/`text`.
|
|
211
|
-
const nativeTheme = resolveStyles && runtimeApplication.type === 'mobile' ? resolveThemeNative((theme ?? undefined)) : undefined;
|
|
212
|
-
const nodeStyleSource = nativeTheme ? undefined : styleSource;
|
|
213
174
|
return {
|
|
214
175
|
page: { id: body.id, name: body.name, slug: body.slug, browserTitle: body.browserTitle, browserTitleKey: body.browserTitleKey, isHomepage: body.isHomepage },
|
|
215
|
-
...(typeof runtimeApplication.type === 'string' ? { applicationType: runtimeApplication.type } : {}),
|
|
216
176
|
events: body.events ?? [],
|
|
217
|
-
layout: layout ? { id: layout.id, name: layout.name, views: asNodes(layout.views).map(node => summarize(node,
|
|
218
|
-
schema: asNodes(body.schema).map(node => summarize(node,
|
|
219
|
-
...(nativeTheme ? { styleResolver: 'native' } : {}),
|
|
177
|
+
layout: layout ? { id: layout.id, name: layout.name, views: asNodes(layout.views).map(node => summarize(node, styleSource)) } : null,
|
|
178
|
+
schema: asNodes(body.schema).map(node => summarize(node, styleSource)),
|
|
220
179
|
widgets: named(body.widgets),
|
|
221
180
|
flows: named(body.flows),
|
|
222
181
|
variableKeys: Object.keys((body.variables ?? {})),
|
|
@@ -235,11 +194,9 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
235
194
|
}
|
|
236
195
|
}
|
|
237
196
|
: {}),
|
|
238
|
-
note:
|
|
239
|
-
? "
|
|
240
|
-
: resolveStyles
|
|
241
|
-
? "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."
|
|
242
|
-
: "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."
|
|
197
|
+
note: resolveStyles
|
|
198
|
+
? "schema, layout'taki slot'un İÇİNE yerleşerek çizilir (Kural 1f). resolvedStyle = componentStyles → preset → düğüm stili kaskadı (aibox'ta tipli grupların derlemesi TABAN katmandır); styleLayers hangi katmanların konuştuğunu söyler — styleLayers.componentPreset ise CSS DEĞİL, düğümün hangi bileşen preseti versiyonundan kurulduğunun işaretidir."
|
|
199
|
+
: "schema, layout'taki slot'un İÇİNE yerleşerek çizilir (Kural 1f); tam gövde gerekirse view:'full'. Bir düğümün ekranda GERÇEKTEN aldığı stili görmek için resolveStyles:true."
|
|
243
200
|
};
|
|
244
201
|
}));
|
|
245
202
|
const screenshotter = injectedScreenshotter ?? new StudioScreenshotter(config);
|
|
@@ -247,7 +204,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
247
204
|
const maxInlinePngBytes = 5 * 1024 * 1024;
|
|
248
205
|
server.registerTool('page_screenshot', {
|
|
249
206
|
title: 'Sayfanın ekran görüntüsünü al',
|
|
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.
|
|
207
|
+
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).",
|
|
251
208
|
inputSchema: {
|
|
252
209
|
project: projectParam,
|
|
253
210
|
environment: environmentParam,
|
|
@@ -256,15 +213,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
256
213
|
viewport: z.object({
|
|
257
214
|
width: z.number().int().min(320).max(3840),
|
|
258
215
|
height: z.number().int().min(240).max(2160)
|
|
259
|
-
}).optional().describe('Varsayılan 1280x800 (deviceScaleFactor 1)
|
|
260
|
-
device: z.union([
|
|
261
|
-
z.enum(['phone', 'tablet']),
|
|
262
|
-
z.object({
|
|
263
|
-
width: z.number().int().min(320).max(3840),
|
|
264
|
-
height: z.number().int().min(240).max(2160),
|
|
265
|
-
scale: z.number().min(1).max(3).optional()
|
|
266
|
-
})
|
|
267
|
-
]).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."),
|
|
216
|
+
}).optional().describe('Varsayılan 1280x800 (deviceScaleFactor 1)'),
|
|
268
217
|
fullPage: z.boolean().optional().describe('true: kaydırılan kısım dahil tüm sayfa; varsayılan yalnız viewport karesi'),
|
|
269
218
|
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)."),
|
|
270
219
|
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."),
|
|
@@ -280,7 +229,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
280
229
|
value: z.unknown().optional().describe("'set' için yazılacak değer. Tarih kutularına ISO yazın ('2026-08-01'); çok alanlı tarih aralığında kök DEĞİL kutu hedeflenir: '<elementId>__start' / '<elementId>__end'. 'tab' için sekmenin ANAHTARI ('iot') ya da görünen metni; 'select' için seçeneğin value'su ya da görünen metni"),
|
|
281
230
|
ms: z.number().int().min(0).max(15000).optional().describe("'wait' için bekleme süresi")
|
|
282
231
|
})).max(20).optional().describe("Kare ALINMADAN ÖNCE sayfada koşturulacak etkileşim adımları: [{action:'set', target:'flt_month', value:'2022-10'}, {action:'click', target:'btn_sign_in'}, {action:'tab', target:'tabs_settings', value:'iot'}, {action:'select', target:'dd_city', value:'34'}, {action:'submit', target:'frm_note'}, {action:'drag', target:'kb_board__card-42', to:'kb_board__col-Assigned'}]. 'tab' SEKME SEÇER: sekme grubunun köküne 'click' göndermek DOM sırasındaki İLK sekmeye gider ve ikinci sekme hiç açılamıyordu — tablist köküne click artık reddedilir ve bu fiile yönlendirir. Eşleme önce '<elementId>__tab-<sekme anahtarı>' kimliğiyle, o yoksa görünen metinle yapılır; actionsReport.matchedBy hangisi olduğunu, actionsReport.activeTab adımdan SONRA hangi sekmenin açık olduğunu söyler. Etkin olmayan sekmenin içeriği DOM'da YOKTUR: o sekmedeki bir düğüme yazmadan önce 'tab' adımı gerekir. 'select' açılır kutudan seçer — kutu native alan değildir, değeri yazmak hiçbir şey yapmaz, seçeneğe tıklanması gerekir; liste 3 sn'ye kadar YOKLANIR (sorgudan beslenen kutuda seçenekler geç basılır) ve actionsReport.optionsSeen + waitedMs neyin görüldüğünü söyler. 'submit' formu gönder butonu OLMADAN gönderir (form.requestSubmit); actionsReport.submitVia hangi yolun kullanıldığını yazar. 'drag' HTML5 sürükle-bırak olaylarını (dragstart→dragover→drop→dragend) hedefin merkezinde atar; kanban cardMove ve takvim eventMove bununla tetiklenir. 'set' native setter kullanır ve input+change olaylarını bubbles ile atar (düz DOM ataması framework'ün duyduğu olayı üretmez); ardından kök düğümün runtime prop'larını yeniden okur: actionsReport.propWritten değerin prop'a ULAŞIP ulaşmadığıdır ve false ise applied da false döner — DOM'a yazılmış ama bileşenin duymadığı değer 'uygulandı' sayılmaz (G-14). Her adımdan sonra yerleşme beklenir ve yanıttaki actionsReport hedefin bulunup bulunmadığını ve adımdan sonra KAÇ AĞ İSTEĞİ çıktığını söyler — 'bağ kuruldu' ile 'çalışıyor' arasındaki farkı ancak bu ayırır (G-24)."),
|
|
283
|
-
language: z.string().min(1).optional().describe("Kareyi BU DİLDE çeker: adrese '?lang=<kod>' eklenir ve runtime dili oradan seçer (öncelik: ?lang → tarayıcıda kayıtlı seçim → tarayıcı dilleri → projenin varsayılanı). Seçim hem çeviriye bağlı prop'ları hem çok dilli alanların satır değerlerini kapsar, yani ikinci dilin karesi için sayfaya dil değiştirici KOYMAK GEREKMEZ — referans tasarımı bozulmadan ikinci dil kanıtlanır. ISO 639-1 iki harfli kod ('tr', 'en'); bölgeli kod ('tr-TR') indirgenir ve indirgeme yanıtta bildirilir. Kod projenin languages listesinde yoksa çağrı REDDEDİLİR: runtime tanımadığı kodu yok sayıp kareyi varsayılan dilde çizerdi ve 'çevrildi' sanılan yanlış bir kare raporlanırdı.
|
|
232
|
+
language: z.string().min(1).optional().describe("Kareyi BU DİLDE çeker: adrese '?lang=<kod>' eklenir ve runtime dili oradan seçer (öncelik: ?lang → tarayıcıda kayıtlı seçim → tarayıcı dilleri → projenin varsayılanı). Seçim hem çeviriye bağlı prop'ları hem çok dilli alanların satır değerlerini kapsar, yani ikinci dilin karesi için sayfaya dil değiştirici KOYMAK GEREKMEZ — referans tasarımı bozulmadan ikinci dil kanıtlanır. ISO 639-1 iki harfli kod ('tr', 'en'); bölgeli kod ('tr-TR') indirgenir ve indirgeme yanıtta bildirilir. Kod projenin languages listesinde yoksa çağrı REDDEDİLİR: runtime tanımadığı kodu yok sayıp kareyi varsayılan dilde çizerdi ve 'çevrildi' sanılan yanlış bir kare raporlanırdı."),
|
|
284
233
|
colorScheme: z.enum(['light', 'dark']).optional().describe("Kareyi açık ya da koyu temada çeker. Ziyaretçi tema tercihi çerezde değil localStorage'da yaşar ve tema store'u onu sayfa açılırken bir kez okur; gateway tercihi sayfa script'lerinden ÖNCE eker, bu yüzden sayfada 'theme-toggle' komponenti OLMASA DA koyu tema karesi alınabilir. Verilmezse projenin kendi modu çizilir."),
|
|
285
234
|
format: z.enum(['png', 'jpeg']).optional().describe("Kare biçimi; varsayılan 'png'. Doğrulama karesi için 'jpeg' + quality 60 bağlamı küçültür (ölçüldü 2026-09-16, aynı sayfa: scale 1'de png 8639 B / jpeg60 8691 B — kazanç yok; scale 0.5'te png 3603 B / jpeg60 3035 B). Asıl kaldıraç ÖLÇEKTİR; renk ve kenar keskinliğinin kendisi inceleniyorsa png'de kalın."),
|
|
286
235
|
quality: z.number().int().min(1).max(100).optional().describe("Yalnız format:'jpeg' ile: 1-100, doğrulama için 60 yeter. format 'png' iken verilirse çağrı reddedilir — PNG kayıpsızdır, kalite ayarı yoktur."),
|
|
@@ -295,7 +244,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
295
244
|
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')
|
|
296
245
|
},
|
|
297
246
|
annotations: readOnly
|
|
298
|
-
}, async ({ project, environment, applicationSlug, pageSlug, viewport,
|
|
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 }) => {
|
|
299
248
|
try {
|
|
300
249
|
if (anonymous === true && (asUser !== undefined || cookies !== undefined)) {
|
|
301
250
|
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.");
|
|
@@ -339,27 +288,10 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
339
288
|
}
|
|
340
289
|
}
|
|
341
290
|
const url = `${config.studioBaseUrl}/${lane}/${projectSlug}/${applicationSlug}/${pageSlug}${languageChoice ? `?lang=${languageChoice.code}` : ''}`;
|
|
342
|
-
|
|
343
|
-
// çizimidir, varsayılan kare telefon ön ayarıdır. Tip okunamazsa web davranışı aynen.
|
|
344
|
-
const resolved = await resolveProfileBySlug(platform, projectSlug, applicationSlug);
|
|
345
|
-
const target = captureTargetFor(resolved.profile.capture.target);
|
|
346
|
-
const mobile = resolved.profile.platform === 'mobile';
|
|
347
|
-
if (device !== undefined && !mobile) {
|
|
348
|
-
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 }).`);
|
|
349
|
-
}
|
|
350
|
-
const deviceName = typeof device === 'string' ? device : device === undefined && mobile ? resolved.profile.capture.defaultDevice : undefined;
|
|
351
|
-
const preset = typeof device === 'object'
|
|
352
|
-
? { width: device.width, height: device.height, dpr: device.scale ?? 1 }
|
|
353
|
-
: deviceName === undefined ? undefined : resolved.profile.capture.devices[deviceName];
|
|
354
|
-
const appliedDevice = viewport === undefined && preset !== undefined ? preset : undefined;
|
|
355
|
-
const deviceNote = viewport !== undefined && device !== undefined
|
|
356
|
-
? 'viewport verildiği için device yok sayıldı: kare verilen viewport ve deviceScaleFactor 1 ile çekildi.'
|
|
357
|
-
: undefined;
|
|
358
|
-
const size = viewport ?? (appliedDevice ? { width: appliedDevice.width, height: appliedDevice.height } : { width: 1280, height: 800 });
|
|
359
|
-
const dpr = appliedDevice && appliedDevice.dpr !== 1 ? appliedDevice.dpr : undefined;
|
|
291
|
+
const size = viewport ?? { width: 1280, height: 800 };
|
|
360
292
|
const extraCookies = Object.entries(cookies ?? {}).map(([name, value]) => ({ name, value }));
|
|
361
293
|
const identity = anonymous === true ? 'anonymous' : asUser === undefined ? 'gateway' : 'asUser';
|
|
362
|
-
const identityToken = anonymous ? undefined :
|
|
294
|
+
const identityToken = anonymous ? undefined : resolveIdentity(asUser) ?? await platform.accessToken();
|
|
363
295
|
const cookieNames = [...new Set([...(identity === 'anonymous' ? [] : ['NG_AccessToken']), ...extraCookies.map(cookie => cookie.name)])];
|
|
364
296
|
const shot = await screenshotter.capture({
|
|
365
297
|
url,
|
|
@@ -381,9 +313,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
381
313
|
...(quality === undefined ? {} : { quality }),
|
|
382
314
|
...(scale === undefined ? {} : { scale }),
|
|
383
315
|
...(clip === undefined ? {} : { clip: clip }),
|
|
384
|
-
...(actions === undefined ? {} : { actions: actions })
|
|
385
|
-
...(mobile ? { target } : {}),
|
|
386
|
-
...(dpr === undefined ? {} : { dpr })
|
|
316
|
+
...(actions === undefined ? {} : { actions: actions })
|
|
387
317
|
});
|
|
388
318
|
// Fark ÖZETTEN çıkar; `summary:false` verilse de fark için toplanmıştır (yanıta girmez).
|
|
389
319
|
// Anahtar KİMLİĞİ ayırır (K8): 'asUser' etiketi yetmez — iki farklı üyenin jetonu aynı
|
|
@@ -454,11 +384,6 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
454
384
|
...(languageChoice?.note ? { languageNote: languageChoice.note } : {}),
|
|
455
385
|
...(colorScheme === undefined ? {} : { colorScheme }),
|
|
456
386
|
viewport: size,
|
|
457
|
-
...(appliedDevice === undefined ? {} : { device: typeof device === 'object' ? { ...device } : deviceName, deviceScaleFactor: appliedDevice.dpr }),
|
|
458
|
-
...(deviceNote === undefined ? {} : { deviceNote }),
|
|
459
|
-
// Web karesinin yanıtı profil öncesiyle aynı kalır (G3 kısıtı); alan yalnız mobilde.
|
|
460
|
-
...(mobile ? applicationTypeField(resolved) : {}),
|
|
461
|
-
...(target.platformNote === undefined ? {} : { platformNote: target.platformNote }),
|
|
462
387
|
fullPage: fullPage === true,
|
|
463
388
|
identity,
|
|
464
389
|
cookieNames,
|
|
@@ -549,14 +474,6 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
549
474
|
runtimeErrors: shot.runtimeErrors,
|
|
550
475
|
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.`
|
|
551
476
|
}),
|
|
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 }),
|
|
560
477
|
...(actionNotes.length === 0 ? {} : { actionsNote: actionNotes.join('\n') }),
|
|
561
478
|
...(shot.layout === undefined
|
|
562
479
|
? {}
|
|
@@ -606,73 +523,31 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
606
523
|
});
|
|
607
524
|
server.registerTool('page_schema_get', {
|
|
608
525
|
title: 'Sayfa şemasını oku',
|
|
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.
|
|
526
|
+
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.",
|
|
610
527
|
inputSchema: {
|
|
611
528
|
project: projectParam,
|
|
612
529
|
environment: environmentParam,
|
|
613
530
|
applicationId: guid,
|
|
614
531
|
pageId: guid,
|
|
615
|
-
mode: modeParam
|
|
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.")
|
|
532
|
+
mode: modeParam,
|
|
533
|
+
componentId: z.string().optional().describe("subtree kipinde kök alınacak komponentin elementId'si")
|
|
619
534
|
},
|
|
620
535
|
annotations: readOnly
|
|
621
|
-
}, handle(config, async ({ project, environment, applicationId, pageId, mode, componentId
|
|
536
|
+
}, handle(config, async ({ project, environment, applicationId, pageId, mode, componentId }) => {
|
|
622
537
|
const page = await fetchPage(platform, project, environment, applicationId, pageId);
|
|
623
|
-
const typeField = applicationTypeField(await resolveProfile(platform, applicationId));
|
|
624
538
|
const updatedAt = page.audit?.updatedAt ?? page.audit?.createdAt;
|
|
625
539
|
const version = page.version;
|
|
626
|
-
|
|
627
|
-
|
|
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
|
-
}
|
|
540
|
+
if (mode === 'full')
|
|
541
|
+
return { version, updatedAt, schema: page.schema ?? [] };
|
|
667
542
|
if (mode === 'subtree') {
|
|
668
543
|
if (!componentId)
|
|
669
544
|
throw new PlatformApiError("mode='subtree' için componentId zorunludur.");
|
|
670
545
|
const subtree = findNode(page.schema, componentId);
|
|
671
546
|
if (!subtree)
|
|
672
547
|
throw new PlatformApiError(`Sayfada '${componentId}' id'li bir komponent yok; page_schema_get'i summary kipinde çağırıp hedefi doğrulayın.`);
|
|
673
|
-
return { version, updatedAt,
|
|
548
|
+
return { version, updatedAt, schema: subtree };
|
|
674
549
|
}
|
|
675
|
-
return { version, updatedAt,
|
|
550
|
+
return { version, updatedAt, schema: (page.schema ?? []).map(node => summarize(node)) };
|
|
676
551
|
}));
|
|
677
552
|
server.registerTool('page_state', {
|
|
678
553
|
title: 'Sayfa durumunu getir',
|
|
@@ -698,25 +573,15 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
698
573
|
...schemaOpValidationErrors(opRecords).map(issue => `PAGE-016 — ${issue}`),
|
|
699
574
|
...codePropTemplateErrors(opRecords).map(issue => `PAGE-032 — ${issue}`)
|
|
700
575
|
];
|
|
701
|
-
const
|
|
702
|
-
const ordering = resolved.profile.layout.orderBy === 'block-position'
|
|
703
|
-
? await orderByIndex(opRecords, () => fetchPage(platform, project, environment, applicationId, pageId))
|
|
704
|
-
: { ops: opRecords, moved: [], warnings: [] };
|
|
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
|
-
});
|
|
576
|
+
const ordering = await orderByIndex(opRecords, () => fetchPage(platform, project, environment, applicationId, pageId));
|
|
711
577
|
const response = ((await platform.tenant('POST', project, `/pages/${applicationId}/${pageId}/schema/validate`, {
|
|
712
578
|
environment,
|
|
713
579
|
body: { ops: [...allOps.filter(entry => entry.op === 'resize_component').flatMap(resizeServerOps), ...ordering.ops] }
|
|
714
580
|
})) ?? {});
|
|
715
581
|
if (validationIssues.length > 0)
|
|
716
582
|
response.validationWarnings = validationIssues;
|
|
717
|
-
if (ordering.warnings.length > 0
|
|
718
|
-
response.layoutWarnings =
|
|
719
|
-
Object.assign(response, applicationTypeField(resolved));
|
|
583
|
+
if (ordering.warnings.length > 0)
|
|
584
|
+
response.layoutWarnings = ordering.warnings;
|
|
720
585
|
return withNormalization(response, [
|
|
721
586
|
...componentDefaultsNote(normalized.components),
|
|
722
587
|
...validationNote(normalized.validations),
|
|
@@ -804,12 +669,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
804
669
|
retargeted = retargetRootAdds(opRecords, PAGE_CONTAINER_ELEMENT_ID);
|
|
805
670
|
}
|
|
806
671
|
}
|
|
807
|
-
const
|
|
808
|
-
const blocksMeaningful = resolved.profile.layout.blocksMeaningful;
|
|
809
|
-
// Mobil (dizi sırası): index platformun kendi add/move index'idir, konum çevirisi yapılmaz.
|
|
810
|
-
const ordering = resolved.profile.layout.orderBy === 'block-position'
|
|
811
|
-
? await orderByIndex(opRecords, readPage)
|
|
812
|
-
: { ops: opRecords, moved: [], warnings: [] };
|
|
672
|
+
const ordering = await orderByIndex(opRecords, readPage);
|
|
813
673
|
const sentOps = ordering.ops;
|
|
814
674
|
// Akış silen op'lar yamadan ÖNCE hesaplanır: uyarı sonradan geldiğinde akış zaten kalıcı
|
|
815
675
|
// silinmiş oluyordu ve "önce yedekle" tavsiyesi uygulanamıyordu (09-05 koşusu, G-21).
|
|
@@ -831,7 +691,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
831
691
|
const added = opRecords
|
|
832
692
|
.filter(entry => entry.op === 'add_component')
|
|
833
693
|
.map(entry => entry.component);
|
|
834
|
-
assertScreenWriteGuards('page', { schema: added }
|
|
694
|
+
assertScreenWriteGuards('page', { schema: added });
|
|
835
695
|
const catalogNotes = await screenCatalogNotes(platform, project, environment, applicationId, added);
|
|
836
696
|
const formWarnings = [...formBindingWarnings(opRecords), ...formSubmitButtonWarnings(opRecords)];
|
|
837
697
|
let flowWarnings = [];
|
|
@@ -869,10 +729,10 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
869
729
|
// görünür, iç içeliği ancak hedef parent zinciri belli olunca anlarsın.
|
|
870
730
|
nestedAfterOps = nestedRepeaterNodes(simulated);
|
|
871
731
|
layoutWarnings = [
|
|
872
|
-
...
|
|
873
|
-
...
|
|
732
|
+
...pageContainerPositionWarning(page.schema),
|
|
733
|
+
...verticalGapWarnings(simulated, 'PAGE-010 (uyarı)', touched),
|
|
874
734
|
...stringStyleWarning(stringStyleNodes(simulated)),
|
|
875
|
-
...
|
|
735
|
+
...inertFlexPropWarning(inertFlexPropNodes(simulated)),
|
|
876
736
|
...flexSizeWarnings(simulated, 'PAGE-017 (uyarı)', 'PAGE-018 (uyarı)'),
|
|
877
737
|
...spacingLayerWarnings(simulated, 'PAGE-029 (uyarı)', 'PAGE-030 (uyarı)')
|
|
878
738
|
];
|
|
@@ -882,51 +742,22 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
882
742
|
}
|
|
883
743
|
}
|
|
884
744
|
if (nestedAfterOps.length > 0)
|
|
885
|
-
throw new PlatformApiError(nestedRepeaterMessage(nestedAfterOps
|
|
745
|
+
throw new PlatformApiError(nestedRepeaterMessage(nestedAfterOps), 400);
|
|
886
746
|
const removedEvents = planEventRemovals(removeEventOps);
|
|
887
747
|
const resized = resizeOps.length > 0 ? planResizes((await readPage()).schema ?? [], resizeOps) : { serverOps: [], applied: [], warnings: [] };
|
|
888
748
|
const propTypes = propTypeOps.length > 0 ? planPropTypes((await readPage()).schema ?? [], propTypeOps) : { serverOps: [], applied: [] };
|
|
889
749
|
const serverOps = [...removeEventOps, ...resized.serverOps, ...propTypes.serverOps, ...sentOps];
|
|
890
|
-
|
|
891
|
-
const profileWarnings = await screenProfileWarnings(platform, resolved, project, environment, applicationId, {
|
|
892
|
-
kind: 'page',
|
|
893
|
-
ops: [...opRecords, ...propTypeOps],
|
|
894
|
-
savedNodes: writesStyle ? await readPage().then(page => page.schema, () => undefined) : undefined,
|
|
895
|
-
steps: await boundFlowSteps(platform, resolved, project, environment, 'page', applicationId, pageId, opRecords)
|
|
896
|
-
});
|
|
897
|
-
layoutWarnings = [...ordering.warnings, ...layoutWarnings, ...profileWarnings];
|
|
750
|
+
layoutWarnings = [...ordering.warnings, ...layoutWarnings];
|
|
898
751
|
// Tek istek, tek sürüm: öykünülen op'lar 2026-09-14'e kadar ayrı GET + tam PUT çiftleriydi ve
|
|
899
752
|
// yamadan ÖNCE yazılıyordu — PATCH reddedilse bile kalıcıydılar. Artık hepsi aynı atomik PATCH'te.
|
|
900
753
|
// baseVersion yalnız biliniyorsa gider (ajanın verdiği ya da bu çağrıda zaten okunan sayfanınki):
|
|
901
754
|
// 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);
|
|
903
755
|
const response = ((await platform.tenant('PATCH', project, `/pages/${applicationId}/${pageId}/schema`, {
|
|
904
756
|
environment,
|
|
905
|
-
body: { ops: serverOps, baseVersion:
|
|
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.");
|
|
757
|
+
body: { ops: serverOps, baseVersion: baseVersion ?? (typeof cachedPage?.version === 'string' ? cachedPage.version : undefined) }
|
|
908
758
|
})) ?? {});
|
|
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
|
-
}
|
|
922
759
|
if (view !== 'full')
|
|
923
760
|
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
|
-
}
|
|
930
761
|
if (removedEvents.length > 0)
|
|
931
762
|
response.removedEvents = removedEvents;
|
|
932
763
|
if (propTypes.applied.length > 0)
|
|
@@ -970,7 +801,6 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
970
801
|
opDrops = emptyWriteDiff();
|
|
971
802
|
}
|
|
972
803
|
}
|
|
973
|
-
Object.assign(response, applicationTypeField(resolved));
|
|
974
804
|
return withWriteDiff(withNormalization(response, [
|
|
975
805
|
...rootAddsNote(retargeted, PAGE_CONTAINER_ELEMENT_ID),
|
|
976
806
|
...componentDefaultsNote(normalized.components),
|
|
@@ -1011,25 +841,20 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
1011
841
|
events: eventsParam.optional(),
|
|
1012
842
|
schema: schemaParam.optional(),
|
|
1013
843
|
view: writeViewParam,
|
|
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
|
|
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")
|
|
1015
845
|
},
|
|
1016
846
|
annotations: writes
|
|
1017
847
|
}, handle(config, async ({ project, environment, applicationId, name, slug, browserTitle, browserTitleKey, layoutId, roleIds, middlewareIds, events, schema, view, ifExists }) => {
|
|
1018
|
-
const
|
|
1019
|
-
|
|
1020
|
-
const container = ensurePageContainer(schema ?? [], resolved.profile.layout.rootContainer);
|
|
1021
|
-
assertScreenWriteGuards('page', { schema: container.schema, events }, resolved.profile.platform);
|
|
848
|
+
const container = ensurePageContainer(schema ?? []);
|
|
849
|
+
assertScreenWriteGuards('page', { schema: container.schema, events });
|
|
1022
850
|
const stringStyles = stringStyleNodes(container.schema);
|
|
1023
851
|
const fixed = normalizeComponentDefaults(container.schema);
|
|
1024
|
-
|
|
1025
|
-
// uyarısı yalnız ızgaranın anlamlı olduğu profilde koşar.
|
|
1026
|
-
const reset = blocksMeaningful ? resetPageContainerPosition(container.schema) : [];
|
|
852
|
+
const reset = resetPageContainerPosition(container.schema);
|
|
1027
853
|
const catalogNotes = (schema ?? []).length > 0
|
|
1028
854
|
? await screenCatalogNotes(platform, project, environment, applicationId, container.schema)
|
|
1029
855
|
: [];
|
|
1030
|
-
const moved =
|
|
1031
|
-
const gaps = (schema ?? []).length > 0
|
|
1032
|
-
const profileWarnings = await screenProfileWarnings(platform, resolved, project, environment, applicationId, { kind: 'page', nodes: container.schema });
|
|
856
|
+
const moved = reorderSchemaBlocks(container.schema);
|
|
857
|
+
const gaps = (schema ?? []).length > 0 ? verticalGapWarnings(container.schema, 'PAGE-010 (uyarı)') : [];
|
|
1033
858
|
const existing = await existingMatch({
|
|
1034
859
|
code: 'PAGE-035',
|
|
1035
860
|
noun: 'sayfa',
|
|
@@ -1054,17 +879,15 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
1054
879
|
const before = pageUpdateBody(current);
|
|
1055
880
|
// Aynı ad/slug ile tekrar koşan betik o alanları "değişti" diye görmemeli (Gözlem 2).
|
|
1056
881
|
const changed = Object.keys(overrides).filter(key => JSON.stringify(before[key]) !== JSON.stringify(overrides[key]));
|
|
1057
|
-
|
|
1058
|
-
rememberVersion(project, environment, pageId, written?.data?.version);
|
|
882
|
+
await platform.tenant('PUT', project, `/pages/${applicationId}/${pageId}`, { environment, body });
|
|
1059
883
|
const updateWarnings = schema === undefined
|
|
1060
884
|
? []
|
|
1061
|
-
: [...gaps, ...stringStyleWarning(stringStyles), ...
|
|
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ı)')];
|
|
1062
886
|
const summary = {
|
|
1063
887
|
id: pageId,
|
|
1064
888
|
outcome: 'updated',
|
|
1065
889
|
changed,
|
|
1066
|
-
preserved: { schemaNodes: countSchemaNodes(body.schema), events: body.events.length }
|
|
1067
|
-
...applicationTypeField(resolved)
|
|
890
|
+
preserved: { schemaNodes: countSchemaNodes(body.schema), events: body.events.length }
|
|
1068
891
|
};
|
|
1069
892
|
if (updateWarnings.length > 0)
|
|
1070
893
|
summary.layoutWarnings = updateWarnings;
|
|
@@ -1087,8 +910,6 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
1087
910
|
const saved = (response && typeof response === 'object' && !Array.isArray(response)
|
|
1088
911
|
? (response.data ?? response)
|
|
1089
912
|
: {});
|
|
1090
|
-
if (typeof saved.id === 'string')
|
|
1091
|
-
rememberVersion(project, environment, saved.id, saved.version);
|
|
1092
913
|
// Kırpmadan ÖNCE doğrulama yolu (B-55): yanıt gövdesi iskelete inince "gönderdiğim prop
|
|
1093
914
|
// kaydedildi mi" sorusunu gövde değil bu diff cevaplar. `layout_create`'in yaptığının sayfa
|
|
1094
915
|
// ikizi; Prop sarmalı açılmazsa doğru kaydedilmiş her prop düşmüş görünür (G-28).
|
|
@@ -1099,17 +920,14 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
1099
920
|
saved.schema = saved.schema.map(node => summarize(node));
|
|
1100
921
|
}
|
|
1101
922
|
const normalized = withWriteDiff(withNormalization(withOutcome(response, undefined), [...container.fixes, ...reset, ...componentDefaultsNote(fixed), ...catalogNotes, ...reorderNote(moved)]), drops);
|
|
1102
|
-
const createWarnings = [...gaps, ...stringStyleWarning(stringStyles), ...
|
|
1103
|
-
|
|
1104
|
-
if (!normalized || typeof normalized !== 'object')
|
|
923
|
+
const createWarnings = [...gaps, ...stringStyleWarning(stringStyles), ...inertFlexPropWarning(inertFlexPropNodes(container.schema)), ...flexSizeWarnings(container.schema, 'PAGE-017 (uyarı)', 'PAGE-018 (uyarı)'), ...spacingLayerWarnings(container.schema, 'PAGE-029 (uyarı)', 'PAGE-030 (uyarı)')];
|
|
924
|
+
if (createWarnings.length === 0 || !normalized || typeof normalized !== 'object')
|
|
1105
925
|
return normalized;
|
|
1106
|
-
|
|
1107
|
-
return Object.keys(typeField).length === 0 ? normalized : { ...normalized, ...typeField };
|
|
1108
|
-
return { ...normalized, ...typeField, layoutWarnings: createWarnings };
|
|
926
|
+
return { ...normalized, layoutWarnings: createWarnings };
|
|
1109
927
|
}));
|
|
1110
928
|
server.registerTool('page_update', {
|
|
1111
929
|
title: 'Sayfa üstbilgisini güncelle',
|
|
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).
|
|
930
|
+
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).",
|
|
1113
931
|
inputSchema: {
|
|
1114
932
|
project: projectParam,
|
|
1115
933
|
environment: environmentParam,
|
|
@@ -1123,61 +941,27 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
1123
941
|
roleIds: z.array(guid).optional(),
|
|
1124
942
|
middlewareIds: z.array(guid).optional(),
|
|
1125
943
|
events: eventsParam.optional(),
|
|
1126
|
-
schema: schemaParam.optional().describe('Nadiren gerekir; verilmezse mevcut şema korunur
|
|
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")
|
|
944
|
+
schema: schemaParam.optional().describe('Nadiren gerekir; verilmezse mevcut şema korunur')
|
|
1130
945
|
},
|
|
1131
946
|
annotations: updates
|
|
1132
|
-
}, handle(config, async ({ project, environment, applicationId, pageId,
|
|
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
|
-
}
|
|
947
|
+
}, handle(config, async ({ project, environment, applicationId, pageId, ...changes }) => {
|
|
1137
948
|
const overrides = Object.fromEntries(Object.entries(changes).filter(([, value]) => value !== undefined));
|
|
1138
|
-
|
|
1139
|
-
assertScreenWriteGuards('page', { schema: overrides.schema, events: overrides.events }, resolved?.profile.platform);
|
|
949
|
+
assertScreenWriteGuards('page', { schema: overrides.schema, events: overrides.events });
|
|
1140
950
|
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
|
-
}
|
|
1153
|
-
const blocksMeaningful = resolved?.profile.layout.blocksMeaningful ?? true;
|
|
1154
951
|
const fixed = normalizeComponentDefaults(overrides.schema);
|
|
1155
|
-
const reset = overrides.schema === undefined
|
|
952
|
+
const reset = overrides.schema === undefined ? [] : resetPageContainerPosition(overrides.schema);
|
|
1156
953
|
const catalogNotes = await screenCatalogNotes(platform, project, environment, applicationId, overrides.schema);
|
|
1157
|
-
const moved = overrides.schema === undefined
|
|
954
|
+
const moved = overrides.schema === undefined ? [] : reorderSchemaBlocks(overrides.schema);
|
|
1158
955
|
const gaps = overrides.schema === undefined
|
|
1159
956
|
? []
|
|
1160
|
-
: [
|
|
1161
|
-
...(blocksMeaningful ? verticalGapWarnings(overrides.schema, 'PAGE-010 (uyarı)') : []),
|
|
1162
|
-
...(resolved?.profile.layout.modes.has('grid') ?? true ? inertFlexPropWarning(inertFlexPropNodes(overrides.schema)) : []),
|
|
1163
|
-
...flexSizeWarnings(overrides.schema, 'PAGE-017 (uyarı)', 'PAGE-018 (uyarı)'),
|
|
1164
|
-
...spacingLayerWarnings(overrides.schema, 'PAGE-029 (uyarı)', 'PAGE-030 (uyarı)'),
|
|
1165
|
-
...(resolved ? await screenProfileWarnings(platform, resolved, project, environment, applicationId, { kind: 'page', nodes: overrides.schema }) : [])
|
|
1166
|
-
];
|
|
957
|
+
: [...verticalGapWarnings(overrides.schema, 'PAGE-010 (uyarı)'), ...inertFlexPropWarning(inertFlexPropNodes(overrides.schema)), ...flexSizeWarnings(overrides.schema, 'PAGE-017 (uyarı)', 'PAGE-018 (uyarı)'), ...spacingLayerWarnings(overrides.schema, 'PAGE-029 (uyarı)', 'PAGE-030 (uyarı)')];
|
|
1167
958
|
const body = pageUpdateBody(current, overrides);
|
|
1168
|
-
|
|
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);
|
|
959
|
+
await platform.tenant('PUT', project, `/pages/${applicationId}/${pageId}`, { environment, body });
|
|
1174
960
|
const summary = {
|
|
1175
961
|
id: pageId,
|
|
1176
962
|
changed: Object.keys(overrides),
|
|
1177
|
-
preserved: { schemaNodes:
|
|
963
|
+
preserved: { schemaNodes: body.schema.length, events: body.events.length }
|
|
1178
964
|
};
|
|
1179
|
-
if (force === true)
|
|
1180
|
-
summary.forced = true;
|
|
1181
965
|
if (gaps.length > 0)
|
|
1182
966
|
summary.layoutWarnings = gaps;
|
|
1183
967
|
return withNormalization(summary, [...reset, ...componentDefaultsNote(fixed), ...catalogNotes, ...reorderNote(moved)]);
|
|
@@ -1188,26 +972,6 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
1188
972
|
inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid, pageId: guid },
|
|
1189
973
|
annotations: writes
|
|
1190
974
|
}, 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
|
-
}));
|
|
1211
975
|
server.registerTool('page_delete', {
|
|
1212
976
|
title: 'Sayfa sil',
|
|
1213
977
|
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.',
|