@appaflytech/zeyrek-ai-mcp 0.0.30 → 0.0.32
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/README.md +5 -5
- package/dist/config.js +0 -37
- package/dist/index.js +0 -2
- package/dist/knowledge/backendConstraints.js +1 -18
- package/dist/knowledge/boxSections.js +184 -0
- package/dist/knowledge/componentCatalog.js +232 -562
- package/dist/knowledge/componentEvents.js +21 -141
- package/dist/knowledge/layoutSummary.js +2 -140
- package/dist/knowledge/normalize.js +67 -236
- package/dist/knowledge/outlines.js +0 -36
- package/dist/knowledge/policy.js +4 -18
- package/dist/knowledge/reservedTables.js +0 -79
- package/dist/knowledge/rules.js +21 -319
- package/dist/knowledge/schemaGuard.js +0 -11
- package/dist/knowledge/styleResolve.js +38 -55
- package/dist/knowledge/themeResolve.js +3 -3
- package/dist/observability/trace.js +3 -6
- package/dist/platform/diagnostics.js +4 -10
- package/dist/platform/domSummary.js +1 -1
- package/dist/platform/errors.js +10 -162
- package/dist/platform/http.js +8 -29
- package/dist/platform/responses.js +5 -39
- package/dist/platform/{capture/capture.js → screenshot.js} +519 -105
- package/dist/platform/secrets.js +1 -17
- package/dist/platform/strictArguments.js +12 -68
- package/dist/platform/tokenVault.js +4 -187
- package/dist/resources/examples/layout-app-shell.json +707 -229
- package/dist/resources/examples/page-detail-profile.json +1 -2
- package/dist/resources/examples/page-dynamic-form.json +2 -3
- package/dist/resources/examples/page-flex-sections.json +1 -1
- package/dist/resources/examples/page-html-embed.json +1 -1
- package/dist/resources/examples/workflow-basic.json +2 -8
- package/dist/resources/guides/component.md +4 -91
- package/dist/resources/guides/entity.md +8 -50
- package/dist/resources/guides/expressions.md +5 -14
- package/dist/resources/guides/functions.md +1 -1
- package/dist/resources/guides/getting-started.md +1 -38
- package/dist/resources/guides/layout.md +58 -55
- package/dist/resources/guides/microservice.md +2 -81
- package/dist/resources/guides/middleware.md +2 -44
- package/dist/resources/guides/page.md +75 -454
- package/dist/resources/guides/theme.md +2 -8
- package/dist/resources/guides/translation.md +1 -1
- package/dist/resources/guides/workflow.md +1 -58
- package/dist/resources/rules.js +5 -11
- package/dist/resources/schema-fingerprint.json +5 -7
- package/dist/resources/schemas/CreateEntityCommand.json +0 -118
- package/dist/resources/schemas/CreateMicroserviceCommand.json +1 -13
- package/dist/resources/schemas/CreateQueryCommand.json +0 -4
- package/dist/resources/schemas/CreateWorkflowCommand.json +1 -143
- package/dist/resources/schemas/DataJobDto.json +1 -6
- package/dist/resources/schemas/GlobalFunctionUpdateResultDto.json +1 -21
- package/dist/resources/schemas/MicroserviceDto.json +3 -15
- package/dist/resources/schemas/PreviewMicroserviceCommand.json +1 -10
- package/dist/resources/schemas/PreviewWorkflowCommand.json +1 -22
- package/dist/resources/schemas/StepDto.json +1 -22
- package/dist/resources/schemas/UpdateEntityCommand.json +0 -118
- package/dist/resources/schemas/UpdateMicroserviceCommand.json +1 -13
- package/dist/resources/schemas/UpdateQueryCommand.json +0 -4
- package/dist/resources/schemas/UpdateViewCommand.json +2 -0
- package/dist/resources/schemas/UpdateWorkflowCommand.json +1 -143
- package/dist/server.js +1 -27
- package/dist/tools/access.js +2 -369
- package/dist/tools/applications.js +3 -60
- package/dist/tools/common.js +0 -7
- package/dist/tools/components.js +14 -118
- package/dist/tools/datasources.js +3 -3
- package/dist/tools/dependencies.js +1 -1
- package/dist/tools/deployments.js +2 -2
- package/dist/tools/designSchemaPatch.js +18 -34
- package/dist/tools/dtos.js +5 -36
- package/dist/tools/entities.js +12 -79
- package/dist/tools/externalComponents.js +5 -26
- package/dist/tools/flows.js +14 -122
- package/dist/tools/folders.js +1 -1
- package/dist/tools/imports.js +2 -5
- package/dist/tools/layouts.js +17 -50
- package/dist/tools/merges.js +1 -1
- package/dist/tools/microservices.js +25 -77
- package/dist/tools/pages.js +112 -465
- package/dist/tools/platformInfo.js +8 -27
- package/dist/tools/projects.js +9 -71
- package/dist/tools/rows.js +13 -69
- package/dist/tools/schemaOps.js +1 -61
- package/dist/tools/screenWrite.js +8 -37
- package/dist/tools/screens.js +5 -95
- package/dist/tools/templates.js +1 -26
- package/dist/tools/translations.js +1 -1
- package/dist/tools/variables.js +5 -11
- package/dist/tools/workflows.js +5 -10
- package/package.json +2 -2
- package/dist/knowledge/access.js +0 -802
- package/dist/knowledge/case.js +0 -41
- package/dist/knowledge/connectDataBind.js +0 -819
- package/dist/knowledge/connectDataDefinition.js +0 -341
- package/dist/knowledge/connectDataExecute.js +0 -93
- package/dist/knowledge/connectDataRules.js +0 -128
- package/dist/knowledge/decision.js +0 -121
- package/dist/knowledge/expressionPrelude.js +0 -59
- package/dist/knowledge/microserviceParameters.js +0 -58
- package/dist/knowledge/mining.js +0 -94
- package/dist/knowledge/mobileComponentCatalog.js +0 -859
- package/dist/knowledge/mobileComponentEvents.js +0 -239
- package/dist/knowledge/platform/lint.js +0 -385
- package/dist/knowledge/platform/mobile.js +0 -142
- package/dist/knowledge/platform/mobileLayoutLint.js +0 -324
- package/dist/knowledge/platform/mobileRules.js +0 -231
- package/dist/knowledge/platform/mobileSpacing.js +0 -82
- package/dist/knowledge/platform/mobileTabs.js +0 -108
- package/dist/knowledge/platform/nativeBoxModel.js +0 -44
- package/dist/knowledge/platform/profile.js +0 -25
- package/dist/knowledge/platform/resolve.js +0 -122
- package/dist/knowledge/platform/web.js +0 -39
- package/dist/knowledge/process.js +0 -510
- package/dist/knowledge/rowFilter.js +0 -43
- package/dist/knowledge/styleResolveNative.js +0 -527
- 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 -122
- 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/impersonation.js +0 -45
- package/dist/platform/jsonArray.js +0 -24
- package/dist/platform/schemaReadMemory.js +0 -60
- package/dist/resources/examples/bpm-gorev-kontrol.json +0 -294
- package/dist/resources/examples/bpm-iso-dokuman.json +0 -228
- package/dist/resources/examples/bpm-satinalma.json +0 -376
- package/dist/resources/examples/connect-data-convert-verify.json +0 -107
- package/dist/resources/examples/connect-data-dropdown-roles.json +0 -66
- package/dist/resources/examples/connect-data-edit-m2m.json +0 -73
- package/dist/resources/examples/connect-data-query-params.json +0 -50
- package/dist/resources/examples/connect-data-table-entity.json +0 -147
- package/dist/resources/examples/connect-data-workflow-microservice.json +0 -65
- package/dist/resources/examples/mobile-app-shell-drawer.json +0 -243
- package/dist/resources/examples/mobile-app-shell-stack.json +0 -157
- package/dist/resources/examples/mobile-app-shell-tab.json +0 -208
- package/dist/resources/examples/mobile-form-screen.json +0 -365
- package/dist/resources/examples/mobile-list-detail-form.json +0 -672
- package/dist/resources/guides/access.md +0 -796
- package/dist/resources/guides/bpm.md +0 -686
- package/dist/resources/guides/case.md +0 -153
- package/dist/resources/guides/decision.md +0 -185
- package/dist/resources/guides/mobile-layout.md +0 -193
- package/dist/resources/guides/mobile.md +0 -495
- package/dist/resources/read.js +0 -41
- package/dist/resources/schemas/AccessExplanationDto.json +0 -144
- package/dist/resources/schemas/AccessMatrixDto.json +0 -170
- package/dist/resources/schemas/AccessPermissionDto.json +0 -57
- package/dist/resources/schemas/AccessPolicyDto.json +0 -223
- package/dist/resources/schemas/AccessResourceDto.json +0 -86
- package/dist/resources/schemas/AppSessionDto.json +0 -53
- package/dist/resources/schemas/ConnectDataDescribeDto.json +0 -344
- package/dist/resources/schemas/ConnectDataRequestModel.json +0 -202
- package/dist/resources/schemas/ConnectDataResultDto.json +0 -421
- package/dist/resources/schemas/ConnectorCredentialDto.json +0 -70
- package/dist/resources/schemas/ConvertConnectDataToMicroserviceCommand.json +0 -37
- package/dist/resources/schemas/CreateAccessPolicyCommand.json +0 -209
- package/dist/resources/schemas/CreateDecisionCommand.json +0 -221
- package/dist/resources/schemas/CreateProcessDefinitionCommand.json +0 -1337
- package/dist/resources/schemas/DecisionDto.json +0 -302
- package/dist/resources/schemas/DetachConnectDataMicroserviceCommand.json +0 -28
- package/dist/resources/schemas/ExplainAccessQuery.json +0 -33
- package/dist/resources/schemas/GenerateProcessDocumentCommand.json +0 -116
- package/dist/resources/schemas/OrganizationProjectDto.json +0 -335
- package/dist/resources/schemas/OrganizationSchemaDto.json +0 -196
- package/dist/resources/schemas/OrganizationSchemaVersionDto.json +0 -207
- package/dist/resources/schemas/PatchProcessDefinitionCommand.json +0 -76
- package/dist/resources/schemas/ProcessDefinitionDto.json +0 -1480
- package/dist/resources/schemas/ProcessSimulationDto.json +0 -96
- package/dist/resources/schemas/SaveConnectorCredentialCommand.json +0 -90
- package/dist/resources/schemas/SaveProcessEventSourceCommand.json +0 -61
- package/dist/resources/schemas/SaveProcessMailSourceCommand.json +0 -89
- package/dist/resources/schemas/SignProcessTaskCommand.json +0 -73
- package/dist/resources/schemas/SimulateProcessDefinitionQuery.json +0 -92
- package/dist/resources/schemas/UpdateAccessPolicyCommand.json +0 -209
- package/dist/resources/schemas/UpdateDecisionCommand.json +0 -216
- package/dist/resources/schemas/UpdateProcessDefinitionCommand.json +0 -1357
- package/dist/resources/schemas/ValidateProcessDefinitionQuery.json +0 -1339
- package/dist/tools/accessOperations.js +0 -135
- package/dist/tools/accessPolicies.js +0 -170
- package/dist/tools/accessRuntime.js +0 -203
- package/dist/tools/bpm.js +0 -343
- package/dist/tools/bpmGovernance.js +0 -266
- package/dist/tools/bpmOperations.js +0 -230
- package/dist/tools/bpmReporting.js +0 -154
- package/dist/tools/bpmRouting.js +0 -150
- package/dist/tools/bpmRuntime.js +0 -396
- package/dist/tools/case.js +0 -178
- package/dist/tools/caseRuntime.js +0 -89
- package/dist/tools/connectData.js +0 -449
- package/dist/tools/connectors.js +0 -193
- package/dist/tools/decisions.js +0 -206
- package/dist/tools/mobile.js +0 -221
- package/dist/tools/modules.js +0 -48
- package/dist/tools/organizationStructure.js +0 -137
- package/dist/tools/profileWrite.js +0 -92
package/dist/tools/pages.js
CHANGED
|
@@ -1,38 +1,28 @@
|
|
|
1
|
-
import { guid, projectParam, environmentParam, pagingParams,
|
|
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 {
|
|
5
|
-
import { fetchHost, pageUpdateBody, schemaParam, eventsParam, unwrap, modeParam, summarize, findNode, flowPath, countSchemaNodes, missingElementIds, splitSchemaIntoParts, encodeSchemaCursor, decodeSchemaCursor } from './screens.js';
|
|
4
|
+
import { fetchHost, pageUpdateBody, schemaParam, eventsParam, unwrap, modeParam, summarize, findNode, flowPath } from './screens.js';
|
|
6
5
|
import { fetchComponentEventNames } from './components.js';
|
|
7
|
-
import { assertScreenWriteGuards, screenCatalogNotes
|
|
8
|
-
import { opsParam, writeViewParam, outlineWriteResponse, unknownEventOps, collectTypes,
|
|
6
|
+
import { assertScreenWriteGuards, screenCatalogNotes } from './screenWrite.js';
|
|
7
|
+
import { opsParam, writeViewParam, outlineWriteResponse, unknownEventOps, collectTypes, simulateLayoutOps, planEventRemovals, resizeServerOps, planResizes, planPropTypes, rejectedOpsMessage } from './schemaOps.js';
|
|
9
8
|
import { formBindingWarnings, formSubmitButtonWarnings } from '../knowledge/coverage.js';
|
|
10
9
|
import { emptyWriteDiff, schemaOpDrops, unwrapProps, withWriteDiff, writeDiff } from '../platform/writeDiff.js';
|
|
11
10
|
import { PlatformApiError } from '../platform/errors.js';
|
|
12
11
|
import { existingMatch, ifExistsParam, listAll, withOutcome } from '../platform/ifExists.js';
|
|
13
|
-
import { handle, toolResult, toolError
|
|
14
|
-
import {
|
|
15
|
-
import { captureTargetFor } from '../platform/capture/target.js';
|
|
12
|
+
import { handle, toolResult, toolError } from '../platform/responses.js';
|
|
13
|
+
import { StudioScreenshotter } from '../platform/screenshot.js';
|
|
16
14
|
import { diffSummaries, summaryLines, summaryStats } from '../platform/domSummary.js';
|
|
17
15
|
import { captureKey, imageHash, sharedCaptureMemory } from '../platform/screenshotMemory.js';
|
|
18
|
-
import { pageReadKey, schemaReadKey, sharedSchemaReadMemory } from '../platform/schemaReadMemory.js';
|
|
19
16
|
import { summarizeLayout } from '../knowledge/layoutSummary.js';
|
|
20
17
|
import { normalizeLanguageCode } from '../knowledge/languageCodes.js';
|
|
21
18
|
import { readProjectLanguages } from '../knowledge/languages.js';
|
|
22
|
-
import {
|
|
23
|
-
import { impersonationHeaders } from '../platform/impersonation.js';
|
|
24
|
-
import { accessDecisionOf, legacyRoleListFindings, projectAccessContext } from '../knowledge/access.js';
|
|
19
|
+
import { resolveIdentityToken } from '../platform/tokenVault.js';
|
|
25
20
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
26
21
|
import { dirname, resolve } from 'node:path';
|
|
27
22
|
import { setPropTypeErrors } from '../knowledge/propTypes.js';
|
|
28
|
-
import {
|
|
23
|
+
import { boxSectionNote, collectIds, ensureBoxSections, expandSectionToggles } from '../knowledge/boxSections.js';
|
|
29
24
|
import { designSnapshotInput } from '../knowledge/designSnapshot.js';
|
|
30
25
|
import { discoverPage } from '../knowledge/pageDiscover.js';
|
|
31
|
-
import { applicationTypeField, resolveProfile, resolveProfileBySlug } from '../knowledge/platform/resolve.js';
|
|
32
|
-
import { resolveThemeNative } from '../knowledge/styleResolveNative.js';
|
|
33
|
-
import { boundFlowSteps, pageLayoutWarnings, screenProfileWarnings } from './profileWrite.js';
|
|
34
|
-
import { applyMobileSpacingDefaults, applyMobileSpacingToOps, emptyMobileSpacingFix, mobileSpacingNote } from '../knowledge/platform/mobileSpacing.js';
|
|
35
|
-
import { shellLessPageSkeleton } from '../knowledge/platform/mobile.js';
|
|
36
26
|
import { z } from 'zod';
|
|
37
27
|
/**
|
|
38
28
|
* Runtime paketindeki `appFunctions`/`pageFunctions` (UiFunctionRuntimeDto: id, name, parameters,
|
|
@@ -67,6 +57,10 @@ async function orderByIndex(ops, readPage) {
|
|
|
67
57
|
return { ops, moved: [], warnings: [] };
|
|
68
58
|
}
|
|
69
59
|
}
|
|
60
|
+
/** Ş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. */
|
|
61
|
+
function countSchemaNodes(nodes) {
|
|
62
|
+
return (nodes ?? []).reduce((total, node) => total + 1 + countSchemaNodes(node.children), 0);
|
|
63
|
+
}
|
|
70
64
|
async function fetchPage(platform, project, environment, applicationId, pageId) {
|
|
71
65
|
const response = await platform.tenant('GET', project, `/pages/${applicationId}/${pageId}`, { environment });
|
|
72
66
|
return response?.data ?? {};
|
|
@@ -79,44 +73,13 @@ async function fetchPage(platform, project, environment, applicationId, pageId)
|
|
|
79
73
|
*/
|
|
80
74
|
export const SERVER_PAGE_OPS = [
|
|
81
75
|
'replace_prop', 'set_binding', 'set_prop_type', 'add_component', 'remove_component', 'move_component', 'reorder', 'set_block',
|
|
82
|
-
'set_appearance', 'set_states', 'set_event', 'add_event', 'remove_event', 'set_validations', 'set_state_binding', 'remove_state_binding', '
|
|
83
|
-
'remove_connect_data', 'batch'
|
|
76
|
+
'set_appearance', 'set_states', 'set_event', 'add_event', 'remove_event', 'set_validations', 'set_state_binding', 'remove_state_binding', 'batch'
|
|
84
77
|
];
|
|
85
|
-
export function registerPageTools(server, platform, injectedScreenshotter, injectedMemory
|
|
78
|
+
export function registerPageTools(server, platform, injectedScreenshotter, injectedMemory) {
|
|
86
79
|
const config = platform.config;
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
const
|
|
90
|
-
const rememberVersion = (project, environment, pageId, version) => {
|
|
91
|
-
if ((typeof version === 'string' && version.length > 0) || typeof version === 'number') {
|
|
92
|
-
schemaReadMemory.rememberVersion(pageKey(project, environment, pageId), String(version));
|
|
93
|
-
}
|
|
94
|
-
};
|
|
95
|
-
/**
|
|
96
|
-
* g-05: sayfa sürüm çakışması (409 `PageSchemaVersionConflictException`) ajana "sayfa hangi
|
|
97
|
-
* sürümde, kim ne zaman değiştirdi" ile döner. Güncel sürüm sayfadan, son yazma revizyon
|
|
98
|
-
* defterinden (`/revisions/{projectId}`, en yeni tek satır) okunur; ikisi de okunamazsa mesaj
|
|
99
|
-
* o parçasız gider — çeviri hiçbir zaman çakışmanın kendisini gizlemez. Başka her hata olduğu
|
|
100
|
-
* gibi döner.
|
|
101
|
-
*/
|
|
102
|
-
const versionConflict = async (error, project, environment, applicationId, pageId, sentVersion, nextStep) => {
|
|
103
|
-
if (!(error instanceof PlatformApiError) || error.code !== 'PageSchemaVersionConflictException')
|
|
104
|
-
return error;
|
|
105
|
-
const currentVersion = await fetchPage(platform, project, environment, applicationId, pageId).then(page => page.version, () => undefined);
|
|
106
|
-
const revision = await (async () => {
|
|
107
|
-
const projectPayload = await platform.org('GET', `/p/${platform.resolveProject(project)}`);
|
|
108
|
-
const revisions = await platform.org('GET', `/revisions/${projectPayload?.data?.id ?? ''}`, {
|
|
109
|
-
query: { environment: environment ?? config.defaultEnvironment, module: 'page', recordId: pageId, pageLength: 1 }
|
|
110
|
-
});
|
|
111
|
-
return revisions?.data?.[0];
|
|
112
|
-
})().catch(() => undefined);
|
|
113
|
-
const actor = (revision?.actor ?? {});
|
|
114
|
-
const lastWrite = revision
|
|
115
|
-
? ` — son yazma ${String(revision.createdAt ?? '?')}, ${String(actor.name ?? actor.id ?? 'bilinmeyen kullanıcı')}, kaynak ${String(revision.source ?? '?')}, revizyon ${String(revision.revisionNo ?? '?')}`
|
|
116
|
-
: '';
|
|
117
|
-
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);
|
|
118
|
-
};
|
|
119
|
-
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. KOMPLEKS PROJEDE referans access_session_create'ten gelir ve page_runtime_get'te Bearer olarak gider (CSRF gerekmez); page_screenshot Studio arayüzünü açtığı için uygulama jetonuyla çekim kompleks projede canlı doğrulanmadı. ÜÇ Ş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.");
|
|
80
|
+
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.");
|
|
81
|
+
/** Sayfa ve uç araçları aynı kimlik sözleşmesini paylaşır (G-20); çözüm tokenVault'ta yaşar. */
|
|
82
|
+
const resolveIdentity = resolveIdentityToken;
|
|
120
83
|
server.registerTool('page_list', {
|
|
121
84
|
title: 'Sayfaları listele',
|
|
122
85
|
description: 'Uygulamanın sayfalarını (id, name, slug) listeler; hedef sayfayı bulmak için ilk adım.',
|
|
@@ -138,14 +101,12 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
138
101
|
}));
|
|
139
102
|
server.registerTool('page_get', {
|
|
140
103
|
title: 'Sayfa üstbilgisini getir',
|
|
141
|
-
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).
|
|
104
|
+
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).",
|
|
142
105
|
inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid, pageId: guid },
|
|
143
106
|
annotations: readOnly
|
|
144
107
|
}, handle(config, async ({ project, environment, applicationId, pageId }) => {
|
|
145
108
|
const page = await fetchPage(platform, project, environment, applicationId, pageId);
|
|
146
|
-
const resolved = await resolveProfile(platform, applicationId);
|
|
147
109
|
const layout = (page.layout ?? {});
|
|
148
|
-
rememberVersion(project, environment, pageId, page.version);
|
|
149
110
|
return {
|
|
150
111
|
id: page.id,
|
|
151
112
|
name: page.name,
|
|
@@ -163,14 +124,12 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
163
124
|
flows: (Array.isArray(page.flows) ? page.flows : [])
|
|
164
125
|
.map(flow => ({ id: flow.id, name: flow.name, slug: flow.slug })),
|
|
165
126
|
schemaNodes: countSchemaNodes(page.schema),
|
|
166
|
-
|
|
167
|
-
updatedAt: page.audit?.updatedAt ?? page.audit?.createdAt,
|
|
168
|
-
...applicationTypeField(resolved)
|
|
127
|
+
updatedAt: page.audit?.updatedAt ?? page.audit?.createdAt
|
|
169
128
|
};
|
|
170
129
|
}));
|
|
171
130
|
server.registerTool('page_runtime_get', {
|
|
172
131
|
title: 'Sayfanın runtime çıktısını getir',
|
|
173
|
-
description: "Çıkan uygulamanın sayfa için aldığı GERÇEK paketi döner (GET /{project}/{application}/p/{slug} — sayfanın ham serve yolu budur; Studio preview ve runtime aynı ucu tüketir): sayfa şeması + layout views'ü + sayfa olayları + flow'lar + değişken anahtarları (variableKeys: middleware değişkenleri, pageVariableKeys/appVariableKeys: state.page/state.app anahtarları) + kullanıcı fonksiyonu imzaları (appFunctions/pageFunctions — bir ifadeye Functions.<ad>(…) yazmadan önce adı ve parametre sırasını buradan alın; gövde taşınmaz, function_get ile okunur). Kurduğun sayfanın SON hâlini doğrulamanın yolu budur (layout-slot yerleşimi dahil — Kural 1f). DİKKAT: sayfaya bağlı middleware'ler bu çağrıda GERÇEKTEN koşar; middleware reddederse yanıt middlewareBlocked ile döner — korumalı sayfada bu beklenen davranıştır ve kendisi bir doğrulamadır.
|
|
132
|
+
description: "Çıkan uygulamanın sayfa için aldığı GERÇEK paketi döner (GET /{project}/{application}/p/{slug} — sayfanın ham serve yolu budur; Studio preview ve runtime aynı ucu tüketir): sayfa şeması + layout views'ü + sayfa olayları + flow'lar + değişken anahtarları (variableKeys: middleware değişkenleri, pageVariableKeys/appVariableKeys: state.page/state.app anahtarları) + kullanıcı fonksiyonu imzaları (appFunctions/pageFunctions — bir ifadeye Functions.<ad>(…) yazmadan önce adı ve parametre sırasını buradan alın; gövde taşınmaz, function_get ile okunur). Kurduğun sayfanın SON hâlini doğrulamanın yolu budur (layout-slot yerleşimi dahil — Kural 1f). DİKKAT: sayfaya bağlı middleware'ler bu çağrıda GERÇEKTEN koşar; middleware reddederse yanıt middlewareBlocked ile döner — korumalı sayfada bu beklenen davranıştır ve kendisi bir doğrulamadır.",
|
|
174
133
|
inputSchema: {
|
|
175
134
|
project: projectParam,
|
|
176
135
|
environment: environmentParam,
|
|
@@ -179,38 +138,33 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
179
138
|
view: z.enum(['summary', 'full']).optional().describe('summary (varsayılan): şema/layout ağaçları id/name/type özetine indirgenir. full: ham gövde — büyük olabilir'),
|
|
180
139
|
resolveStyles: z.boolean().optional().describe("summary görünümünde her düğüme EFEKTİF stilini ekler: tema componentStyles → preset → düğümün kendi stili kaskadı çözülür (aibox'ta tipli stil grupları da derlenir ve TABAN katman olur). styleLayers hangi katmanın konuştuğunu söyler — 'bu kutu neden böyle görünüyor' sorusunun cevabı budur."),
|
|
181
140
|
anonymous: z.boolean().optional().describe("true: istek HİÇBİR Authorization header'ı ve çerezi olmadan gider — 'korumalı sayfa tokensız ne döner, şema sızıyor mu?' sorusunun cevabı budur. Korumalı sayfada middlewareBlocked bekleyin."),
|
|
182
|
-
asUser: asUserParam
|
|
183
|
-
asUserId: asUserIdParam
|
|
141
|
+
asUser: asUserParam
|
|
184
142
|
},
|
|
185
143
|
annotations: readOnly
|
|
186
|
-
}, handle(config, async ({ project, environment, applicationSlug, pageSlug, view, resolveStyles, anonymous, asUser
|
|
144
|
+
}, handle(config, async ({ project, environment, applicationSlug, pageSlug, view, resolveStyles, anonymous, asUser }) => {
|
|
187
145
|
let response;
|
|
188
146
|
if (anonymous === true && asUser !== undefined) {
|
|
189
147
|
throw new PlatformApiError('page_runtime_get: anonymous:true ile asUser birlikte verilemez — biri kimliksiz isteği, diğeri belirli bir üyenin kimliğiyle atılan isteği ister.');
|
|
190
148
|
}
|
|
191
149
|
// page_screenshot çerez, bu tool başlık taşıyordu; çerez okuyan middleware ikincisinden
|
|
192
150
|
// yapısal olarak geçemiyordu. Kimlik artık iki kanalda birden gider (Y-02).
|
|
193
|
-
const
|
|
194
|
-
const identity = asUser === undefined ? (eye ? { headers: eye } : {}) : { headers: { authorization: `Bearer ${await resolveIdentity(asUser, platform, project, environment)}` } };
|
|
151
|
+
const identity = asUser === undefined ? {} : { headers: { authorization: `Bearer ${resolveIdentity(asUser)}` } };
|
|
195
152
|
try {
|
|
196
153
|
response = await platform.tenant('GET', project, `/${applicationSlug}/p/${pageSlug}`, { environment, anonymous, mirrorAuthorizationCookie: true, ...identity });
|
|
197
154
|
}
|
|
198
155
|
catch (error) {
|
|
199
156
|
if (error instanceof PlatformApiError && typeof error.status === 'number' && error.status >= 300) {
|
|
200
|
-
const decision = accessDecisionOf(await projectAccessContext(platform, project, environment), error.status, error.code, error.apiMessage);
|
|
201
157
|
return {
|
|
202
158
|
middlewareBlocked: true,
|
|
203
159
|
status: error.status,
|
|
204
160
|
message: error.message,
|
|
205
|
-
...(decision ? { accessDecision: decision } : {}),
|
|
206
161
|
note: "Sayfaya bağlı middleware isteği reddetti ya da kayıt bulunamadı. Korumalı sayfada kimliksiz/yetkisiz isteğin reddedilmesi BEKLENEN davranıştır. Sayfanın bağlarını page_get ile, middleware tarifini platform_guide('middleware') ile kontrol edin; slug'ları page_list/application_list doğrular."
|
|
207
162
|
};
|
|
208
163
|
}
|
|
209
164
|
throw error;
|
|
210
165
|
}
|
|
211
|
-
const decision = accessDecisionOf(await projectAccessContext(platform, project, environment), 200);
|
|
212
166
|
if (view === 'full')
|
|
213
|
-
return
|
|
167
|
+
return response;
|
|
214
168
|
const body = (response?.data ?? {});
|
|
215
169
|
const layout = body.layout;
|
|
216
170
|
const theme = body.project?.theme;
|
|
@@ -218,19 +172,11 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
218
172
|
const styleSource = resolveStyles ? (theme ?? null) : undefined;
|
|
219
173
|
const asNodes = (value) => (Array.isArray(value) ? value : []);
|
|
220
174
|
const named = (value) => (Array.isArray(value) ? value : []).map(entry => ({ id: entry.id, name: entry.name, slug: entry.slug }));
|
|
221
|
-
const runtimeApplication = (body.application ?? {});
|
|
222
|
-
// Mobil uygulama React Native stiliyle çizilir (G4): rapor CSS kaskadı değil `shell`/`text`.
|
|
223
|
-
const nativeTheme = resolveStyles && runtimeApplication.type === 'mobile' ? resolveThemeNative((theme ?? undefined)) : undefined;
|
|
224
|
-
// Mobilde de tema katmanı (componentStyles ← $preset) çizerin tabanıdır (f-16); rapor aynı kaynağı okur.
|
|
225
|
-
const nodeStyleSource = styleSource;
|
|
226
175
|
return {
|
|
227
|
-
...(decision ? { accessDecision: decision } : {}),
|
|
228
176
|
page: { id: body.id, name: body.name, slug: body.slug, browserTitle: body.browserTitle, browserTitleKey: body.browserTitleKey, isHomepage: body.isHomepage },
|
|
229
|
-
...(typeof runtimeApplication.type === 'string' ? { applicationType: runtimeApplication.type } : {}),
|
|
230
177
|
events: body.events ?? [],
|
|
231
|
-
layout: layout ? { id: layout.id, name: layout.name, views: asNodes(layout.views).map(node => summarize(node,
|
|
232
|
-
schema: asNodes(body.schema).map(node => summarize(node,
|
|
233
|
-
...(nativeTheme ? { styleResolver: 'native' } : {}),
|
|
178
|
+
layout: layout ? { id: layout.id, name: layout.name, views: asNodes(layout.views).map(node => summarize(node, styleSource)) } : null,
|
|
179
|
+
schema: asNodes(body.schema).map(node => summarize(node, styleSource)),
|
|
234
180
|
widgets: named(body.widgets),
|
|
235
181
|
flows: named(body.flows),
|
|
236
182
|
variableKeys: Object.keys((body.variables ?? {})),
|
|
@@ -249,11 +195,9 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
249
195
|
}
|
|
250
196
|
}
|
|
251
197
|
: {}),
|
|
252
|
-
note:
|
|
253
|
-
? "
|
|
254
|
-
: resolveStyles
|
|
255
|
-
? "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."
|
|
256
|
-
: "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."
|
|
198
|
+
note: resolveStyles
|
|
199
|
+
? "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."
|
|
200
|
+
: "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."
|
|
257
201
|
};
|
|
258
202
|
}));
|
|
259
203
|
const screenshotter = injectedScreenshotter ?? new StudioScreenshotter(config);
|
|
@@ -261,7 +205,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
261
205
|
const maxInlinePngBytes = 5 * 1024 * 1024;
|
|
262
206
|
server.registerTool('page_screenshot', {
|
|
263
207
|
title: 'Sayfanın ekran görüntüsünü al',
|
|
264
|
-
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.
|
|
208
|
+
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).",
|
|
265
209
|
inputSchema: {
|
|
266
210
|
project: projectParam,
|
|
267
211
|
environment: environmentParam,
|
|
@@ -270,23 +214,14 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
270
214
|
viewport: z.object({
|
|
271
215
|
width: z.number().int().min(320).max(3840),
|
|
272
216
|
height: z.number().int().min(240).max(2160)
|
|
273
|
-
}).optional().describe('Varsayılan 1280x800 (deviceScaleFactor 1)
|
|
274
|
-
device: z.union([
|
|
275
|
-
z.enum(['phone', 'tablet']),
|
|
276
|
-
z.object({
|
|
277
|
-
width: z.number().int().min(320).max(3840),
|
|
278
|
-
height: z.number().int().min(240).max(2160),
|
|
279
|
-
scale: z.number().min(1).max(3).optional()
|
|
280
|
-
})
|
|
281
|
-
]).optional().describe("Yalnız MOBİL uygulama: cihaz ön ayarı — 'phone' 390x844 @2x (varsayılan), 'tablet' 820x1180 @2x, ya da { width, height, scale? }. Kare cihaz piksel yoğunluğunda çıkar (phone → 780x1688 px); raster küçültme için scale parametresi bununla çarpılır. Web uygulamasında reddedilir: dar ekran için viewport verin."),
|
|
217
|
+
}).optional().describe('Varsayılan 1280x800 (deviceScaleFactor 1)'),
|
|
282
218
|
fullPage: z.boolean().optional().describe('true: kaydırılan kısım dahil tüm sayfa; varsayılan yalnız viewport karesi'),
|
|
283
219
|
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)."),
|
|
284
220
|
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."),
|
|
285
221
|
anonymous: z.boolean().optional().describe("true: hiçbir çerez enjekte edilmez — anonim ziyaretçinin gördüğü kare. Korumalı sayfada middlewareBlocked bekleyin."),
|
|
286
222
|
asUser: asUserParam,
|
|
287
|
-
asUserId: asUserIdParam.describe("Parolasız kullanıcı gözüyle kare (users satır id'si): tarayıcı gateway jetonuyla açılır ve sayfanın BÜTÜN istekleri 'impersonated-user-id' başlığını taşır — Studio önizlemesindeki 'View as user' seçicisinin yaptığının aynısı. Kompleks enforce projede gateway jetonu tek başına 401 alır; kullanıcı gözüyle kare bu alanla alınır. Yalnız development ya da dal (production'da çağrı reddedilir); asUser/anonymous/cookies ile birlikte verilemez."),
|
|
288
223
|
expectFailure: z.boolean().optional().describe("true: bu çekimde bir akış adımının düşmesi BEKLENİYOR (kasıtlı negatif test — yanlış parola, yetkisiz kullanıcı, geçersiz gövde). Kare yine alınır ve runtimeErrors yine dolar, ama yanıt expectedFailure:true taşır ve benchmark raporu bu satırı plansız hata saymaz: 2026-09-06 koşusunda on başarısız çağrının ALTISI kasıtlı negatif testti ve ayıklama elle yapıldı (S-4)."),
|
|
289
|
-
layoutReport: z.boolean().optional().describe("true: kareyle birlikte her runtime düğümünün efektif piksel kutusunu ve özetini (taşan / viewport dışı / sıfır boyutlu elementId'ler) döner — taşma ve çakışmayı görüntüsüz, sayısal yakalar.
|
|
224
|
+
layoutReport: z.boolean().optional().describe("true: kareyle birlikte her runtime düğümünün efektif piksel kutusunu ve özetini (taşan / viewport dışı / sıfır boyutlu elementId'ler) döner — taşma ve çakışmayı görüntüsüz, sayısal yakalar. Düğümler data-element-id taşımıyorsa layoutReportUnavailable döner."),
|
|
290
225
|
cookies: z.record(z.string()).optional().describe("Tarayıcıya enjekte edilecek EK çerezler (ad → değer): uygulamanın kendi oturum çerezi buraya verilir. Aynı ada sahip çerez gateway jetonunun yerine geçer. anonymous ile birlikte verilemez."),
|
|
291
226
|
actions: z.array(z.object({
|
|
292
227
|
action: z.enum(['set', 'click', 'wait', 'drag', 'tab', 'select', 'submit']),
|
|
@@ -295,9 +230,8 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
295
230
|
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"),
|
|
296
231
|
ms: z.number().int().min(0).max(15000).optional().describe("'wait' için bekleme süresi")
|
|
297
232
|
})).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)."),
|
|
298
|
-
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ı.
|
|
233
|
+
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ı."),
|
|
299
234
|
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."),
|
|
300
|
-
motion: z.enum(['frozen', 'play']).optional().describe("Varsayılan 'frozen': bağlam 'hareketi azalt' (prefers-reduced-motion: reduce) ile açılır; mobil runtime hareketi dondurur ve kare tekrarlanabilir olur — diff'in imageUnchanged'ı ancak böyle anlamlıdır. Web sayfasında yalnız bu tercihe uyan CSS animasyonları durur. 'play': hareket oynar (giriş animasyonu, dönen gösterge, iskelet parıltısı); kare çekildiği ana bağlıdır, iki kare bayt bayt aynı çıkmayabilir ve yanıt bunu motionNote ile söyler. diff belleği iki kipi ayrı tutar."),
|
|
301
235
|
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."),
|
|
302
236
|
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."),
|
|
303
237
|
scale: z.number().min(0.25).max(1).optional().describe("Karenin RASTER ölçeği (deviceScaleFactor); varsayılan 1. 0.5 verildiğinde sayfa yine 1280 CSS pikseli genişliğinde ÇİZİLİR ama kare 640 piksel çıkar: yerleşim, kırılım noktaları ve hideOn davranışı değişmez, yalnız görüntü küçülür. Dar ekran denemek için scale değil viewport kullanın. Yanıttaki imageSize ölçeğin gerçekten uygulandığını sayıyla gösterir."),
|
|
@@ -306,12 +240,12 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
306
240
|
z.object({ x: z.number(), y: z.number(), width: z.number().positive(), height: z.number().positive() })
|
|
307
241
|
]).optional().describe("Kareyi bir bölgeyle sınırlar: { element: '<elementId>' } o düğümün kutusunu, { x, y, width, height } elle verilen dikdörtgeni (CSS pikseli) kırpar — yan sütunu ya da tek kartı görmek için tüm sayfayı çekip elle kırpmaya gerek yok. Kutu katlanmış alanın altındaysa viewport onu kapsayana kadar BÜYÜTÜLÜR ve kutu yeniden okunur; düğüm yoksa ya da 0x0 çizildiyse çağrı boş kare yerine HATA döner."),
|
|
308
242
|
summary: z.union([z.enum(['compact', 'full']), z.literal(false)]).optional().describe("Sayfada NE GÖRÜNDÜĞÜNÜN metin özeti; varsayılan 'compact'. Her KÖK runtime düğümü için tek satır: kimlik (tip) · görünür|GİZLİ|ekran dışı · metnin ilk 80 karakteri · rows=<satır> · tab=<açık sekme> · value=<kutudaki değer> · BOŞ · errors=<sayı>; sonda toast ve hata sayfası mesajları. 'full' bunun yanına yapısal domSummary dizisini ve messages'ı koyar; false hiç toplamaz. 'Kart göründü mü, tablo doldu mu, hata var mı' sorularının cevabı buradadır — o sorular için KAREYE GEREK YOKTUR (image:'none' ile birlikte kullanın). Tip alanı runtime'ın data-component-type basmasına bağlıdır; basmıyorsa tipler yazılmaz ve summaryNote bunu söyler."),
|
|
309
|
-
diff: z.boolean().optional().describe("true: aynı sayfanın ÖNCEKİ çekimine göre ne değiştiğini döner (changes.added / removed / changed / messages) ve karenin bayt bayt aynı olup olmadığını söyler (imageUnchanged). Bellek gateway SÜRECİ içindedir: yeniden başlatılınca silinir ve ilk çağrı changes.firstCapture: true döner; sonraki çağrılar baselineAt taşır. Anahtar adres, kimlik, dil, tema,
|
|
243
|
+
diff: z.boolean().optional().describe("true: aynı sayfanın ÖNCEKİ çekimine göre ne değiştiğini döner (changes.added / removed / changed / messages) ve karenin bayt bayt aynı olup olmadığını söyler (imageUnchanged). Bellek gateway SÜRECİ içindedir: yeniden başlatılınca silinir ve ilk çağrı changes.firstCapture: true döner; sonraki çağrılar baselineAt taşır. Anahtar adres, kimlik, dil, tema, viewport, fullPage, actions, clip, scale ve biçimdir — asUser karesi gateway karesiyle karşılaştırılmaz."),
|
|
310
244
|
image: z.enum(['inline', 'none', 'ifChanged']).optional().describe("'none': kare HİÇ ÇEKİLMEZ, yanıt yalnız özet ve ölçümlerden oluşur — olgu doğrulamasının (kart göründü mü, tablo kaç satır) maliyeti bir kareden iki-üç kat küçüktür ve model bağlamına görüntü girmez. Varsayılan 'inline'. 'ifChanged': kare çekilir ama ÖNCEKİ çekimle bayt bayt aynıysa yanıta KONULMAZ (imageSkipped: true) — aynı sayfayı yeniden koştururken değişmemiş kareyi ikinci kez okumak bağlamın boşa gitmesidir; diff:true ima edilir. outPath ile 'none' birlikte verilemez (yazılacak kare üretilmiyor). Renk, hizalama, referansa sadakat gibi GÖRSEL sorular için kareyi açık bırakın."),
|
|
311
245
|
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')
|
|
312
246
|
},
|
|
313
247
|
annotations: readOnly
|
|
314
|
-
}, async ({ project, environment, applicationSlug, pageSlug, viewport,
|
|
248
|
+
}, 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 }) => {
|
|
315
249
|
try {
|
|
316
250
|
if (anonymous === true && (asUser !== undefined || cookies !== undefined)) {
|
|
317
251
|
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.");
|
|
@@ -354,38 +288,16 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
354
288
|
throw new PlatformApiError(`page_screenshot: '${languageChoice.code}' projenin dilleri arasında değil (${state.languages.join(', ')}). Runtime bilmediği kodu yok sayıp kareyi varsayılan dilde çizer — dili project_update(languages) ile ekleyin ya da listedeki bir kodu verin.`);
|
|
355
289
|
}
|
|
356
290
|
}
|
|
357
|
-
if (asUserId !== undefined && cookies !== undefined) {
|
|
358
|
-
throw new PlatformApiError("page_screenshot: asUserId ile cookies birlikte verilemez — uygulama oturum çerezi (ZY_App_…) geçerliyse platform başlığı değil çerezin sahibini özne yapar ve karenin kimin gözüyle çekildiği belirsiz kalır.");
|
|
359
|
-
}
|
|
360
|
-
const eye = await impersonationHeaders(platform, 'page_screenshot', project, environment, { asUserId, asUser, anonymous }, { runtime: true });
|
|
361
291
|
const url = `${config.studioBaseUrl}/${lane}/${projectSlug}/${applicationSlug}/${pageSlug}${languageChoice ? `?lang=${languageChoice.code}` : ''}`;
|
|
362
|
-
|
|
363
|
-
// çizimidir, varsayılan kare telefon ön ayarıdır. Tip okunamazsa web davranışı aynen.
|
|
364
|
-
const resolved = await resolveProfileBySlug(platform, projectSlug, applicationSlug);
|
|
365
|
-
const target = captureTargetFor(resolved.profile.capture.target);
|
|
366
|
-
const mobile = resolved.profile.platform === 'mobile';
|
|
367
|
-
if (device !== undefined && !mobile) {
|
|
368
|
-
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 }).`);
|
|
369
|
-
}
|
|
370
|
-
const deviceName = typeof device === 'string' ? device : device === undefined && mobile ? resolved.profile.capture.defaultDevice : undefined;
|
|
371
|
-
const preset = typeof device === 'object'
|
|
372
|
-
? { width: device.width, height: device.height, dpr: device.scale ?? 1 }
|
|
373
|
-
: deviceName === undefined ? undefined : resolved.profile.capture.devices[deviceName];
|
|
374
|
-
const appliedDevice = viewport === undefined && preset !== undefined ? preset : undefined;
|
|
375
|
-
const deviceNote = viewport !== undefined && device !== undefined
|
|
376
|
-
? 'viewport verildiği için device yok sayıldı: kare verilen viewport ve deviceScaleFactor 1 ile çekildi.'
|
|
377
|
-
: undefined;
|
|
378
|
-
const size = viewport ?? (appliedDevice ? { width: appliedDevice.width, height: appliedDevice.height } : { width: 1280, height: 800 });
|
|
379
|
-
const dpr = appliedDevice && appliedDevice.dpr !== 1 ? appliedDevice.dpr : undefined;
|
|
292
|
+
const size = viewport ?? { width: 1280, height: 800 };
|
|
380
293
|
const extraCookies = Object.entries(cookies ?? {}).map(([name, value]) => ({ name, value }));
|
|
381
|
-
const identity = anonymous === true ? 'anonymous' : asUser
|
|
382
|
-
const identityToken = anonymous ? undefined :
|
|
294
|
+
const identity = anonymous === true ? 'anonymous' : asUser === undefined ? 'gateway' : 'asUser';
|
|
295
|
+
const identityToken = anonymous ? undefined : resolveIdentity(asUser) ?? await platform.accessToken();
|
|
383
296
|
const cookieNames = [...new Set([...(identity === 'anonymous' ? [] : ['NG_AccessToken']), ...extraCookies.map(cookie => cookie.name)])];
|
|
384
297
|
const shot = await screenshotter.capture({
|
|
385
298
|
url,
|
|
386
299
|
apiPathMarker: `/${applicationSlug}/p/${pageSlug}`,
|
|
387
300
|
token: identityToken,
|
|
388
|
-
...(eye ? { headers: eye } : {}),
|
|
389
301
|
...(extraCookies.length > 0 ? { cookies: extraCookies } : {}),
|
|
390
302
|
cookieUrl: config.studioBaseUrl,
|
|
391
303
|
viewport: size,
|
|
@@ -394,7 +306,6 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
394
306
|
...(captureUnready === true ? { captureUnready: true } : {}),
|
|
395
307
|
...(layoutReport === true ? { layoutReport: true } : {}),
|
|
396
308
|
...(colorScheme === undefined ? {} : { colorScheme }),
|
|
397
|
-
...(motion === 'play' ? { motion } : {}),
|
|
398
309
|
// B-2: `summary:false` + `diff` verildiğinde özet yine toplanır — fark ona dayanır ve
|
|
399
310
|
// toplanmazsa bellek hiç yazılmaz, `ifChanged` sessizce ölü bir özellik olurdu.
|
|
400
311
|
...(summary === false && !(diff === true || image === 'ifChanged') ? { summary: false } : {}),
|
|
@@ -403,9 +314,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
403
314
|
...(quality === undefined ? {} : { quality }),
|
|
404
315
|
...(scale === undefined ? {} : { scale }),
|
|
405
316
|
...(clip === undefined ? {} : { clip: clip }),
|
|
406
|
-
...(actions === undefined ? {} : { actions: actions })
|
|
407
|
-
...(mobile ? { target } : {}),
|
|
408
|
-
...(dpr === undefined ? {} : { dpr })
|
|
317
|
+
...(actions === undefined ? {} : { actions: actions })
|
|
409
318
|
});
|
|
410
319
|
// Fark ÖZETTEN çıkar; `summary:false` verilse de fark için toplanmıştır (yanıta girmez).
|
|
411
320
|
// Anahtar KİMLİĞİ ayırır (K8): 'asUser' etiketi yetmez — iki farklı üyenin jetonu aynı
|
|
@@ -415,12 +324,10 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
415
324
|
const memoryKey = captureKey({
|
|
416
325
|
url,
|
|
417
326
|
identity,
|
|
418
|
-
|
|
419
|
-
principal: asUserId === undefined ? identityToken : `${identityToken ?? ''}|${asUserId}`,
|
|
327
|
+
principal: identityToken,
|
|
420
328
|
cookieValues: extraCookies.map(cookie => `${cookie.name}=${cookie.value}`),
|
|
421
329
|
language: languageChoice?.code,
|
|
422
330
|
colorScheme,
|
|
423
|
-
motion: motion ?? 'frozen',
|
|
424
331
|
viewport: size,
|
|
425
332
|
fullPage: fullPage === true,
|
|
426
333
|
actions,
|
|
@@ -477,13 +384,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
477
384
|
...(languageChoice ? { language: languageChoice.code } : {}),
|
|
478
385
|
...(languageChoice?.note ? { languageNote: languageChoice.note } : {}),
|
|
479
386
|
...(colorScheme === undefined ? {} : { colorScheme }),
|
|
480
|
-
...(motion === 'play' ? { motion, motionNote: 'Hareket açık: kare çekildiği ana bağlıdır, aynı sayfanın iki karesi bayt bayt aynı çıkmayabilir (diff imageUnchanged hareketi ölçmez). Tekrarlanabilir kare için motion verilmez.' } : {}),
|
|
481
387
|
viewport: size,
|
|
482
|
-
...(appliedDevice === undefined ? {} : { device: typeof device === 'object' ? { ...device } : deviceName, deviceScaleFactor: appliedDevice.dpr }),
|
|
483
|
-
...(deviceNote === undefined ? {} : { deviceNote }),
|
|
484
|
-
// Web karesinin yanıtı profil öncesiyle aynı kalır (G3 kısıtı); alan yalnız mobilde.
|
|
485
|
-
...(mobile ? applicationTypeField(resolved) : {}),
|
|
486
|
-
...(target.platformNote === undefined ? {} : { platformNote: target.platformNote }),
|
|
487
388
|
fullPage: fullPage === true,
|
|
488
389
|
identity,
|
|
489
390
|
cookieNames,
|
|
@@ -528,7 +429,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
528
429
|
: {}),
|
|
529
430
|
...(shot.apiStatus === undefined ? {} : { apiStatus: shot.apiStatus }),
|
|
530
431
|
...(shot.apiStatus !== undefined && shot.apiStatus >= 300
|
|
531
|
-
? { middlewareBlocked: true, note: `Sayfanın veri isteği reddedildi — karede hata/boş durum görünüyor olabilir. Korumalı sayfada kimliksiz/yetkisiz isteğin reddedilmesi BEKLENEN davranıştır (page_runtime_get ile karşılaştırın).${identity === 'gateway' ? " Gateway'in servis jetonu uygulamanın kendi kullanıcı tablosuna çözülmeyebilir
|
|
432
|
+
? { middlewareBlocked: true, note: `Sayfanın veri isteği reddedildi — karede hata/boş durum görünüyor olabilir. Korumalı sayfada kimliksiz/yetkisiz isteğin reddedilmesi BEKLENEN davranıştır (page_runtime_get ile karşılaştırın).${identity === 'gateway' ? " Gateway'in servis jetonu uygulamanın kendi kullanıcı tablosuna çözülmeyebilir; üye kimliğiyle çekmek için asUser (ve gerekiyorsa cookies) verin. Uygulamanın 'users' tablosu boşsa hiçbir kimlik çözülmez; yeni projede o tablo BOŞ gelir — row_list('users') ile bakın, ilk kullanıcıyı açma yolu platform_guide('getting-started') §'İlk kullanıcıyı kim açar?'." : ''}` }
|
|
532
433
|
: {}),
|
|
533
434
|
// P1-1 (S-3): eşzamanlı çekimler gateway içinde SERİ koşar; ajan yavaşlığın kuyruktan mı
|
|
534
435
|
// yoksa sayfadan mı geldiğini bilsin.
|
|
@@ -546,7 +447,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
546
447
|
readyAfterMs: shot.readyAfterMs,
|
|
547
448
|
...(shot.finalUrl === undefined ? {} : { finalUrl: shot.finalUrl }),
|
|
548
449
|
...(shot.pageState === 'ready-no-flows'
|
|
549
|
-
? { pageStateNote: 'Runtime çizildi ama hiç mount akışı koşmadı. Sayfa akışsızsa (giriş ekranı)
|
|
450
|
+
? { pageStateNote: 'Runtime çizildi ama hiç mount akışı koşmadı. Sayfa akışsızsa (giriş ekranı) normaldir; veri bekliyorsanız page_schema_get ile mount olayına bağlı akış olup olmadığına bakın.' }
|
|
550
451
|
: shot.pageState === 'ready-no-counter'
|
|
551
452
|
? { pageStateNote: 'Runtime koşan akış sayacı yayınlamıyor (eski runtime); hazırlık DOM durulmasından çıkarıldı, akışların bittiği kanıtlanamadı.' }
|
|
552
453
|
: shot.pageState === 'error'
|
|
@@ -572,26 +473,15 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
572
473
|
? {}
|
|
573
474
|
: {
|
|
574
475
|
runtimeErrors: shot.runtimeErrors,
|
|
575
|
-
runtimeErrorsNote:
|
|
576
|
-
}),
|
|
577
|
-
...(shot.expressionErrors === undefined
|
|
578
|
-
? {}
|
|
579
|
-
: { expressionErrors: shot.expressionErrors, expressionErrorsNote: expressionErrorsNote(shot.expressionErrors) }),
|
|
580
|
-
...(shot.renderErrors === undefined
|
|
581
|
-
? {}
|
|
582
|
-
: {
|
|
583
|
-
renderErrors: shot.renderErrors,
|
|
584
|
-
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ı).`
|
|
476
|
+
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.`
|
|
585
477
|
}),
|
|
586
|
-
...(shot.pageErrors === undefined ? {} : { pageErrors: shot.pageErrors }),
|
|
587
|
-
...(shot.consoleErrors === undefined ? {} : { consoleErrors: shot.consoleErrors }),
|
|
588
478
|
...(actionNotes.length === 0 ? {} : { actionsNote: actionNotes.join('\n') }),
|
|
589
479
|
...(shot.layout === undefined
|
|
590
480
|
? {}
|
|
591
481
|
: shot.layout.length === 0
|
|
592
482
|
? { layoutReportUnavailable: true, note: "Sayfada data-element-id taşıyan düğüm yok — runtime bu attribute'u henüz basmıyor olabilir (F-04) ya da sayfa boş render edildi." }
|
|
593
483
|
: {
|
|
594
|
-
layoutSummary: summarizeLayout(shot.layout, size
|
|
484
|
+
layoutSummary: summarizeLayout(shot.layout, size),
|
|
595
485
|
...(shot.layout.length <= 100 ? { layout: shot.layout } : { layoutTruncatedTo: 'summary' })
|
|
596
486
|
})
|
|
597
487
|
};
|
|
@@ -634,73 +524,31 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
634
524
|
});
|
|
635
525
|
server.registerTool('page_schema_get', {
|
|
636
526
|
title: 'Sayfa şemasını oku',
|
|
637
|
-
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.
|
|
527
|
+
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.",
|
|
638
528
|
inputSchema: {
|
|
639
529
|
project: projectParam,
|
|
640
530
|
environment: environmentParam,
|
|
641
531
|
applicationId: guid,
|
|
642
532
|
pageId: guid,
|
|
643
|
-
mode: modeParam
|
|
644
|
-
componentId: z.string().optional().describe("subtree kipinde kök alınacak komponentin elementId'si")
|
|
645
|
-
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."),
|
|
646
|
-
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.")
|
|
533
|
+
mode: modeParam,
|
|
534
|
+
componentId: z.string().optional().describe("subtree kipinde kök alınacak komponentin elementId'si")
|
|
647
535
|
},
|
|
648
536
|
annotations: readOnly
|
|
649
|
-
}, handle(config, async ({ project, environment, applicationId, pageId, mode, componentId
|
|
537
|
+
}, handle(config, async ({ project, environment, applicationId, pageId, mode, componentId }) => {
|
|
650
538
|
const page = await fetchPage(platform, project, environment, applicationId, pageId);
|
|
651
|
-
const typeField = applicationTypeField(await resolveProfile(platform, applicationId));
|
|
652
539
|
const updatedAt = page.audit?.updatedAt ?? page.audit?.createdAt;
|
|
653
540
|
const version = page.version;
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
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);
|
|
657
|
-
}
|
|
658
|
-
if (mode === 'full') {
|
|
659
|
-
const schema = page.schema ?? [];
|
|
660
|
-
const nodeCount = countSchemaNodes(schema);
|
|
661
|
-
const versionKey = String(version ?? '');
|
|
662
|
-
if (cursor !== undefined) {
|
|
663
|
-
const decoded = decodeSchemaCursor(cursor);
|
|
664
|
-
if (!decoded)
|
|
665
|
-
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);
|
|
666
|
-
if (decoded.version !== undefined && decoded.version !== versionKey) {
|
|
667
|
-
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);
|
|
668
|
-
}
|
|
669
|
-
const parts = splitSchemaIntoParts(schema, Math.floor(config.maxResponseBytes * 0.75));
|
|
670
|
-
const items = parts[decoded.partIndex];
|
|
671
|
-
if (!items)
|
|
672
|
-
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);
|
|
673
|
-
const nextIndex = decoded.partIndex + 1;
|
|
674
|
-
return {
|
|
675
|
-
version,
|
|
676
|
-
updatedAt,
|
|
677
|
-
...typeField,
|
|
678
|
-
nodeCount,
|
|
679
|
-
part: { index: decoded.partIndex, count: parts.length, nodes: countSchemaNodes(items.map(item => item.node)) },
|
|
680
|
-
items,
|
|
681
|
-
...(nextIndex < parts.length ? { nextCursor: encodeSchemaCursor(versionKey, nextIndex) } : {})
|
|
682
|
-
};
|
|
683
|
-
}
|
|
684
|
-
const full = { version, updatedAt, ...typeField, nodeCount, schema };
|
|
685
|
-
const chars = (JSON.stringify(prune(full)) ?? '').length;
|
|
686
|
-
if (chars > config.maxResponseBytes) {
|
|
687
|
-
if (allowTruncated !== true) {
|
|
688
|
-
const kb = (value) => (value / 1024).toFixed(1);
|
|
689
|
-
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);
|
|
690
|
-
}
|
|
691
|
-
schemaReadMemory.markTruncated(readKey(project, environment, pageId, version));
|
|
692
|
-
}
|
|
693
|
-
return full;
|
|
694
|
-
}
|
|
541
|
+
if (mode === 'full')
|
|
542
|
+
return { version, updatedAt, schema: page.schema ?? [] };
|
|
695
543
|
if (mode === 'subtree') {
|
|
696
544
|
if (!componentId)
|
|
697
545
|
throw new PlatformApiError("mode='subtree' için componentId zorunludur.");
|
|
698
546
|
const subtree = findNode(page.schema, componentId);
|
|
699
547
|
if (!subtree)
|
|
700
548
|
throw new PlatformApiError(`Sayfada '${componentId}' id'li bir komponent yok; page_schema_get'i summary kipinde çağırıp hedefi doğrulayın.`);
|
|
701
|
-
return { version, updatedAt,
|
|
549
|
+
return { version, updatedAt, schema: subtree };
|
|
702
550
|
}
|
|
703
|
-
return { version, updatedAt,
|
|
551
|
+
return { version, updatedAt, schema: (page.schema ?? []).map(node => summarize(node)) };
|
|
704
552
|
}));
|
|
705
553
|
server.registerTool('page_state', {
|
|
706
554
|
title: 'Sayfa durumunu getir',
|
|
@@ -721,55 +569,25 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
721
569
|
// Kuru koşu ile yazma AYNI gövdeyi görür (P0-2 Step 3): resize_component burada da sunucu
|
|
722
570
|
// op'larına çevrilir, platform dokunulan düğümleri aynı kapsamla doğrular.
|
|
723
571
|
const opRecords = allOps.filter(entry => entry.op !== 'resize_component');
|
|
724
|
-
const
|
|
725
|
-
// ARC-143: kuru koşu page_schema_patch'in add_component kapısını (PAGE-009 · 011 · 012 · 013)
|
|
726
|
-
// atlıyordu; ayrılmış adlı komponent validate'ten geçip yamada reddediliyordu.
|
|
727
|
-
assertScreenWriteGuards('page', { schema: opRecords.filter(entry => entry.op === 'add_component').map(entry => entry.component) }, resolved.profile.platform);
|
|
728
|
-
const readPage = () => fetchPage(platform, project, environment, applicationId, pageId);
|
|
729
|
-
const normalized = normalizeSchemaOps(opRecords, await styleTargetTypes(opRecords, readPage));
|
|
572
|
+
const normalized = normalizeSchemaOps(opRecords);
|
|
730
573
|
const validationIssues = [
|
|
731
574
|
...schemaOpValidationErrors(opRecords).map(issue => `PAGE-016 — ${issue}`),
|
|
732
575
|
...codePropTemplateErrors(opRecords).map(issue => `PAGE-032 — ${issue}`)
|
|
733
576
|
];
|
|
734
|
-
const
|
|
735
|
-
const ordering = resolved.profile.layout.orderBy === 'block-position'
|
|
736
|
-
? await orderByIndex(opRecords, () => fetchPage(platform, project, environment, applicationId, pageId))
|
|
737
|
-
: { ops: opRecords, moved: [], warnings: [] };
|
|
738
|
-
const writesStyle = resolved.profile.platform !== 'web' && opRecords.some(entry => (entry.op === 'replace_prop' || entry.op === 'set_binding') && entry.prop === 'style');
|
|
739
|
-
const profileWarnings = await screenProfileWarnings(platform, resolved, project, environment, applicationId, {
|
|
740
|
-
kind: 'page',
|
|
741
|
-
ops: opRecords,
|
|
742
|
-
savedNodes: writesStyle ? await fetchPage(platform, project, environment, applicationId, pageId).then(page => page.schema, () => undefined) : undefined
|
|
743
|
-
});
|
|
744
|
-
// page_schema_patch'in Connect Data duvarı kuru koşuda da koşar: patch'in reddedeceği op burada görünür.
|
|
745
|
-
const connectedProps = opRecords.some(entry => ['set_binding', 'set_prop_type', 'replace_prop', 'set_connect_data', 'remove_connect_data'].includes(String(entry.op)))
|
|
746
|
-
? await fetchPage(platform, project, environment, applicationId, pageId).then(page => connectedPropKeys(page.schema), () => null)
|
|
747
|
-
: null;
|
|
748
|
-
const connectDataRejections = opRecords.flatMap((entry, index) => connectDataOpIssues(entry, connectedProps).map(issue => ({
|
|
749
|
-
index,
|
|
750
|
-
op: String(entry.op ?? ''),
|
|
751
|
-
target: typeof entry.target === 'string' ? entry.target : null,
|
|
752
|
-
code: issue.code,
|
|
753
|
-
message: issue.message
|
|
754
|
-
})));
|
|
577
|
+
const ordering = await orderByIndex(opRecords, () => fetchPage(platform, project, environment, applicationId, pageId));
|
|
755
578
|
const response = ((await platform.tenant('POST', project, `/pages/${applicationId}/${pageId}/schema/validate`, {
|
|
756
579
|
environment,
|
|
757
580
|
body: { ops: [...allOps.filter(entry => entry.op === 'resize_component').flatMap(resizeServerOps), ...ordering.ops] }
|
|
758
581
|
})) ?? {});
|
|
759
|
-
const skeletonWarnings = await simulatedPageLayoutWarnings(platform, resolved, project, environment, applicationId, () => fetchPage(platform, project, environment, applicationId, pageId), opRecords, 'audit');
|
|
760
582
|
if (validationIssues.length > 0)
|
|
761
583
|
response.validationWarnings = validationIssues;
|
|
762
|
-
if (ordering.warnings.length > 0
|
|
763
|
-
response.layoutWarnings =
|
|
764
|
-
if (connectDataRejections.length > 0)
|
|
765
|
-
response.rejectedOps = connectDataRejections;
|
|
766
|
-
Object.assign(response, applicationTypeField(resolved));
|
|
584
|
+
if (ordering.warnings.length > 0)
|
|
585
|
+
response.layoutWarnings = ordering.warnings;
|
|
767
586
|
return withNormalization(response, [
|
|
768
587
|
...componentDefaultsNote(normalized.components),
|
|
769
588
|
...validationNote(normalized.validations),
|
|
770
589
|
...replacePropNote(normalized.props),
|
|
771
|
-
...flexIndexOrderNote(ordering.moved)
|
|
772
|
-
...mobileSpacingNote(spacing)
|
|
590
|
+
...flexIndexOrderNote(ordering.moved)
|
|
773
591
|
]);
|
|
774
592
|
}));
|
|
775
593
|
server.registerTool('page_schema_patch', {
|
|
@@ -820,25 +638,23 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
820
638
|
throw new PlatformApiError(`Sayfa siz okuduktan sonra değişmiş (updatedAt: ${updatedAt}). page_schema_get ile yeniden okuyup planı tazeleyin.`, 409);
|
|
821
639
|
}
|
|
822
640
|
}
|
|
823
|
-
|
|
641
|
+
// BX-002: bölge anahtarı Studio'da düğüm kurar; aynısı yamaya burada eklenir ki eklenen
|
|
642
|
+
// bölge düğümü de aşağıdaki normalizasyon ve doğrulama duvarından geçsin.
|
|
643
|
+
let sectionNotes = [];
|
|
644
|
+
if (opRecords.some(entry => entry.op === 'add_component' || (entry.op === 'replace_prop' && (entry.prop === 'hasHeader' || entry.prop === 'hasFooter')))) {
|
|
645
|
+
const schema = await readPage().then(page => page.schema ?? []).catch(() => []);
|
|
646
|
+
const taken = collectIds(schema, new Set());
|
|
647
|
+
const addedBodies = opRecords.filter(entry => entry.op === 'add_component').map(entry => entry.component);
|
|
648
|
+
collectIds(addedBodies, taken);
|
|
649
|
+
const created = ensureBoxSections(addedBodies, taken);
|
|
650
|
+
const toggles = expandSectionToggles(opRecords, schema, taken);
|
|
651
|
+
sectionNotes = boxSectionNote([...created, ...toggles.created], toggles.removed);
|
|
652
|
+
}
|
|
653
|
+
const normalized = normalizeSchemaOps(opRecords);
|
|
824
654
|
for (const entry of opRecords)
|
|
825
655
|
reject('PAGE-016', entry, schemaOpValidationErrors([entry]));
|
|
826
656
|
for (const entry of opRecords)
|
|
827
657
|
reject('PAGE-032', entry, codePropTemplateErrors([entry]));
|
|
828
|
-
const touchesConnection = opRecords.some(entry => entry.op === 'set_binding' || entry.op === 'set_prop_type' || entry.op === 'replace_prop' || entry.op === 'remove_connect_data');
|
|
829
|
-
let connectedProps = null;
|
|
830
|
-
if (touchesConnection) {
|
|
831
|
-
try {
|
|
832
|
-
connectedProps = connectedPropKeys((await readPage()).schema);
|
|
833
|
-
}
|
|
834
|
-
catch {
|
|
835
|
-
connectedProps = null;
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
for (const entry of opRecords) {
|
|
839
|
-
for (const issue of connectDataOpIssues(entry, connectedProps))
|
|
840
|
-
reject(issue.code, entry, [issue.message]);
|
|
841
|
-
}
|
|
842
658
|
if (opRecords.some(entry => entry.op === 'set_event' || entry.op === 'add_event')) {
|
|
843
659
|
const typesByElementId = new Map();
|
|
844
660
|
try {
|
|
@@ -866,21 +682,14 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
866
682
|
retargeted = retargetRootAdds(opRecords, PAGE_CONTAINER_ELEMENT_ID);
|
|
867
683
|
}
|
|
868
684
|
}
|
|
869
|
-
const
|
|
870
|
-
const spacing = await mobileSpacingForOps(resolved.profile.platform, opRecords, readPage);
|
|
871
|
-
const blocksMeaningful = resolved.profile.layout.blocksMeaningful;
|
|
872
|
-
// Mobil (dizi sırası): index platformun kendi add/move index'idir, konum çevirisi yapılmaz.
|
|
873
|
-
const ordering = resolved.profile.layout.orderBy === 'block-position'
|
|
874
|
-
? await orderByIndex(opRecords, readPage)
|
|
875
|
-
: { ops: opRecords, moved: [], warnings: [] };
|
|
685
|
+
const ordering = await orderByIndex(opRecords, readPage);
|
|
876
686
|
const sentOps = ordering.ops;
|
|
877
687
|
// Akış silen op'lar yamadan ÖNCE hesaplanır: uyarı sonradan geldiğinde akış zaten kalıcı
|
|
878
688
|
// silinmiş oluyordu ve "önce yedekle" tavsiyesi uygulanamıyordu (09-05 koşusu, G-21).
|
|
879
689
|
let deletedFlows = [];
|
|
880
690
|
// remove_event opRecords'ta değil, ayrı listede durur; koşul yalnız opRecords'a baktığı sürece
|
|
881
691
|
// yalnız olay kaldıran bir yama bu korumayı hiç tetiklemiyordu (2026-09-14).
|
|
882
|
-
|
|
883
|
-
if ([...opRecords, ...removeEventOps].some(entry => ['remove_component', 'set_event', 'remove_event', 'batch'].includes(String(entry.op)))) {
|
|
692
|
+
if ([...opRecords, ...removeEventOps].some(entry => ['remove_component', 'set_event', 'remove_event'].includes(String(entry.op)))) {
|
|
884
693
|
try {
|
|
885
694
|
const page = await readPage();
|
|
886
695
|
deletedFlows = orphanedFlowPlan(page.schema, page.events, [...opRecords, ...removeEventOps]);
|
|
@@ -895,7 +704,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
895
704
|
const added = opRecords
|
|
896
705
|
.filter(entry => entry.op === 'add_component')
|
|
897
706
|
.map(entry => entry.component);
|
|
898
|
-
assertScreenWriteGuards('page', { schema: added }
|
|
707
|
+
assertScreenWriteGuards('page', { schema: added });
|
|
899
708
|
const catalogNotes = await screenCatalogNotes(platform, project, environment, applicationId, added);
|
|
900
709
|
const formWarnings = [...formBindingWarnings(opRecords), ...formSubmitButtonWarnings(opRecords)];
|
|
901
710
|
let flowWarnings = [];
|
|
@@ -933,10 +742,10 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
933
742
|
// görünür, iç içeliği ancak hedef parent zinciri belli olunca anlarsın.
|
|
934
743
|
nestedAfterOps = nestedRepeaterNodes(simulated);
|
|
935
744
|
layoutWarnings = [
|
|
936
|
-
...
|
|
937
|
-
...
|
|
745
|
+
...pageContainerPositionWarning(page.schema),
|
|
746
|
+
...verticalGapWarnings(simulated, 'PAGE-010 (uyarı)', touched),
|
|
938
747
|
...stringStyleWarning(stringStyleNodes(simulated)),
|
|
939
|
-
...
|
|
748
|
+
...inertFlexPropWarning(inertFlexPropNodes(simulated)),
|
|
940
749
|
...flexSizeWarnings(simulated, 'PAGE-017 (uyarı)', 'PAGE-018 (uyarı)'),
|
|
941
750
|
...spacingLayerWarnings(simulated, 'PAGE-029 (uyarı)', 'PAGE-030 (uyarı)')
|
|
942
751
|
];
|
|
@@ -946,54 +755,22 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
946
755
|
}
|
|
947
756
|
}
|
|
948
757
|
if (nestedAfterOps.length > 0)
|
|
949
|
-
throw new PlatformApiError(nestedRepeaterMessage(nestedAfterOps
|
|
758
|
+
throw new PlatformApiError(nestedRepeaterMessage(nestedAfterOps), 400);
|
|
950
759
|
const removedEvents = planEventRemovals(removeEventOps);
|
|
951
760
|
const resized = resizeOps.length > 0 ? planResizes((await readPage()).schema ?? [], resizeOps) : { serverOps: [], applied: [], warnings: [] };
|
|
952
761
|
const propTypes = propTypeOps.length > 0 ? planPropTypes((await readPage()).schema ?? [], propTypeOps) : { serverOps: [], applied: [] };
|
|
953
762
|
const serverOps = [...removeEventOps, ...resized.serverOps, ...propTypes.serverOps, ...sentOps];
|
|
954
|
-
|
|
955
|
-
const profileWarnings = await screenProfileWarnings(platform, resolved, project, environment, applicationId, {
|
|
956
|
-
kind: 'page',
|
|
957
|
-
ops: [...opRecords, ...propTypeOps],
|
|
958
|
-
savedNodes: writesStyle ? await readPage().then(page => page.schema, () => undefined) : undefined,
|
|
959
|
-
steps: await boundFlowSteps(platform, resolved, project, environment, 'page', applicationId, pageId, opRecords)
|
|
960
|
-
});
|
|
961
|
-
const skeletonWarnings = reshapesSkeleton(opRecords)
|
|
962
|
-
? await simulatedPageLayoutWarnings(platform, resolved, project, environment, applicationId, readPage, sentOps, 'new')
|
|
963
|
-
: [];
|
|
964
|
-
layoutWarnings = [...ordering.warnings, ...layoutWarnings, ...profileWarnings, ...skeletonWarnings];
|
|
763
|
+
layoutWarnings = [...ordering.warnings, ...layoutWarnings];
|
|
965
764
|
// Tek istek, tek sürüm: öykünülen op'lar 2026-09-14'e kadar ayrı GET + tam PUT çiftleriydi ve
|
|
966
765
|
// yamadan ÖNCE yazılıyordu — PATCH reddedilse bile kalıcıydılar. Artık hepsi aynı atomik PATCH'te.
|
|
967
766
|
// baseVersion yalnız biliniyorsa gider (ajanın verdiği ya da bu çağrıda zaten okunan sayfanınki):
|
|
968
767
|
// sürümü öğrenmek için ayrı GET atılmaz, platform satırı her yamada kilitler.
|
|
969
|
-
const sentVersion = baseVersion ?? (typeof cachedPage?.version === 'string' ? cachedPage.version : undefined);
|
|
970
768
|
const response = ((await platform.tenant('PATCH', project, `/pages/${applicationId}/${pageId}/schema`, {
|
|
971
769
|
environment,
|
|
972
|
-
body: { ops: serverOps, baseVersion:
|
|
973
|
-
}).catch(async (error) => {
|
|
974
|
-
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.");
|
|
770
|
+
body: { ops: serverOps, baseVersion: baseVersion ?? (typeof cachedPage?.version === 'string' ? cachedPage.version : undefined) }
|
|
975
771
|
})) ?? {});
|
|
976
|
-
const patched = (response.data && typeof response.data === 'object' && !Array.isArray(response.data) ? response.data : response);
|
|
977
|
-
const unchanged = Array.isArray(patched.unchanged) ? patched.unchanged.map(String) : [];
|
|
978
|
-
rememberVersion(project, environment, pageId, patched.newVersion);
|
|
979
|
-
// g-05: platform (b-05) aynı yere aynı içerikle zaten duran add_component'i etkisiz sayar; bütün
|
|
980
|
-
// op'lar etkisizse yazmaz ve eski baseVersion'a 409 demez. Ajan bunu "hata yok ama bir şey de
|
|
981
|
-
// olmadı" diye okumasın: yanıtı kaybolan çağrının tekrarı olduğu açıkça söylenir.
|
|
982
|
-
if (unchanged.length > 0) {
|
|
983
|
-
response.alreadyApplied = patched.noop !== true
|
|
984
|
-
? `Şu op'lar sayfada zaten duruyordu, yeniden yazılmadı (tekrar zararsız): ${unchanged.join(', ')}. Diğer op'lar uygulandı.`
|
|
985
|
-
: patched.stale === true
|
|
986
|
-
? `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.`
|
|
987
|
-
: `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.`;
|
|
988
|
-
}
|
|
989
772
|
if (view !== 'full')
|
|
990
773
|
outlineWriteResponse(response);
|
|
991
|
-
if (baseVersion && schemaReadMemory.wasTruncated(readKey(project, environment, pageId, baseVersion))) {
|
|
992
|
-
response.warnings = [
|
|
993
|
-
...(Array.isArray(response.warnings) ? response.warnings : []),
|
|
994
|
-
`PAGE-042 (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.`
|
|
995
|
-
];
|
|
996
|
-
}
|
|
997
774
|
if (removedEvents.length > 0)
|
|
998
775
|
response.removedEvents = removedEvents;
|
|
999
776
|
if (propTypes.applied.length > 0)
|
|
@@ -1037,15 +814,14 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
1037
814
|
opDrops = emptyWriteDiff();
|
|
1038
815
|
}
|
|
1039
816
|
}
|
|
1040
|
-
Object.assign(response, applicationTypeField(resolved));
|
|
1041
817
|
return withWriteDiff(withNormalization(response, [
|
|
1042
818
|
...rootAddsNote(retargeted, PAGE_CONTAINER_ELEMENT_ID),
|
|
819
|
+
...sectionNotes,
|
|
1043
820
|
...componentDefaultsNote(normalized.components),
|
|
1044
821
|
...validationNote(normalized.validations),
|
|
1045
822
|
...catalogNotes,
|
|
1046
823
|
...replacePropNote(normalized.props),
|
|
1047
|
-
...flexIndexOrderNote(ordering.moved)
|
|
1048
|
-
...mobileSpacingNote(spacing)
|
|
824
|
+
...flexIndexOrderNote(ordering.moved)
|
|
1049
825
|
]), opDrops);
|
|
1050
826
|
}));
|
|
1051
827
|
server.registerTool('page_discover', {
|
|
@@ -1076,38 +852,23 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
1076
852
|
layoutId: guid.optional(),
|
|
1077
853
|
roleIds: z.array(guid).optional().describe('Sayfayı görebilecek roller; boş = herkese açık'),
|
|
1078
854
|
middlewareIds: z.array(guid).optional(),
|
|
1079
|
-
isAnonymous: z.boolean().optional().describe('Anonim uç: true ise sayfa kompleks projede oturumsuz da açılır (erişim kapısı atlanır). Giriş ekranı ve genel açılış sayfaları için; veri gösteren sayfalarda açmayın.'),
|
|
1080
|
-
isLoginPage: z.boolean().optional().describe("Uygulamanın giriş sayfası. Bir application'da yalnız bir sayfa bu bayrağı taşımalıdır; sayfa reddi (403) bu sayfaya yönlendirilir. Giriş sayfası oturumsuz açılabilmelidir, bu yüzden isAnonymous ile birlikte kullanın."),
|
|
1081
855
|
events: eventsParam.optional(),
|
|
1082
856
|
schema: schemaParam.optional(),
|
|
1083
857
|
view: writeViewParam,
|
|
1084
|
-
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
|
|
858
|
+
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")
|
|
1085
859
|
},
|
|
1086
860
|
annotations: writes
|
|
1087
|
-
}, handle(config, async ({ project, environment, applicationId, name, slug, browserTitle, browserTitleKey, layoutId, roleIds, middlewareIds,
|
|
1088
|
-
const
|
|
1089
|
-
|
|
1090
|
-
const container = ensurePageContainer(schema ?? [], resolved.profile.layout.rootContainer);
|
|
1091
|
-
const spacing = emptyMobileSpacingFix();
|
|
1092
|
-
// Kabuksuz iskelet yalnız gateway'in kurduğu köke uygulanır; sayfanın layout'u yazımda kesinleşir.
|
|
1093
|
-
const templateRoot = resolved.profile.platform === 'mobile' && container.fixes.length > 0 ? container.schema[0] : undefined;
|
|
1094
|
-
let skeletonNotes = [];
|
|
1095
|
-
if (resolved.profile.platform === 'mobile') {
|
|
1096
|
-
for (const root of container.schema)
|
|
1097
|
-
applyMobileSpacingDefaults(root.children, [], spacing, String(root.elementId ?? ''));
|
|
1098
|
-
}
|
|
1099
|
-
assertScreenWriteGuards('page', { schema: container.schema, events }, resolved.profile.platform);
|
|
861
|
+
}, handle(config, async ({ project, environment, applicationId, name, slug, browserTitle, browserTitleKey, layoutId, roleIds, middlewareIds, events, schema, view, ifExists }) => {
|
|
862
|
+
const container = ensurePageContainer(schema ?? []);
|
|
863
|
+
assertScreenWriteGuards('page', { schema: container.schema, events });
|
|
1100
864
|
const stringStyles = stringStyleNodes(container.schema);
|
|
1101
865
|
const fixed = normalizeComponentDefaults(container.schema);
|
|
1102
|
-
|
|
1103
|
-
// uyarısı yalnız ızgaranın anlamlı olduğu profilde koşar.
|
|
1104
|
-
const reset = blocksMeaningful ? resetPageContainerPosition(container.schema) : [];
|
|
866
|
+
const reset = resetPageContainerPosition(container.schema);
|
|
1105
867
|
const catalogNotes = (schema ?? []).length > 0
|
|
1106
|
-
? await
|
|
868
|
+
? await screenCatalogNotes(platform, project, environment, applicationId, container.schema)
|
|
1107
869
|
: [];
|
|
1108
|
-
const moved =
|
|
1109
|
-
const gaps = (schema ?? []).length > 0
|
|
1110
|
-
const profileWarnings = await screenProfileWarnings(platform, resolved, project, environment, applicationId, { kind: 'page', nodes: container.schema });
|
|
870
|
+
const moved = reorderSchemaBlocks(container.schema);
|
|
871
|
+
const gaps = (schema ?? []).length > 0 ? verticalGapWarnings(container.schema, 'PAGE-010 (uyarı)') : [];
|
|
1111
872
|
const existing = await existingMatch({
|
|
1112
873
|
code: 'PAGE-035',
|
|
1113
874
|
noun: 'sayfa',
|
|
@@ -1124,36 +885,28 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
1124
885
|
if (existing !== undefined) {
|
|
1125
886
|
const pageId = String(existing.id);
|
|
1126
887
|
const current = await fetchHost(platform, 'page', project, environment, applicationId, pageId);
|
|
1127
|
-
if (templateRoot && schema !== undefined && (layoutId ?? current.layoutId ?? current.layout?.id ?? null) === null) {
|
|
1128
|
-
skeletonNotes = shellLessPageSkeleton(templateRoot);
|
|
1129
|
-
}
|
|
1130
888
|
const overrides = Object.fromEntries(Object.entries({
|
|
1131
|
-
name, slug, browserTitle, browserTitleKey, layoutId, roleIds, middlewareIds,
|
|
889
|
+
name, slug, browserTitle, browserTitleKey, layoutId, roleIds, middlewareIds, events,
|
|
1132
890
|
schema: schema === undefined ? undefined : container.schema
|
|
1133
891
|
}).filter(([, value]) => value !== undefined));
|
|
1134
892
|
const body = pageUpdateBody(current, overrides);
|
|
1135
893
|
const before = pageUpdateBody(current);
|
|
1136
894
|
// Aynı ad/slug ile tekrar koşan betik o alanları "değişti" diye görmemeli (Gözlem 2).
|
|
1137
895
|
const changed = Object.keys(overrides).filter(key => JSON.stringify(before[key]) !== JSON.stringify(overrides[key]));
|
|
1138
|
-
|
|
1139
|
-
rememberVersion(project, environment, pageId, written?.data?.version);
|
|
896
|
+
await platform.tenant('PUT', project, `/pages/${applicationId}/${pageId}`, { environment, body });
|
|
1140
897
|
const updateWarnings = schema === undefined
|
|
1141
898
|
? []
|
|
1142
|
-
: [...gaps, ...stringStyleWarning(stringStyles), ...
|
|
1143
|
-
...await pageLayoutWarnings(platform, resolved, project, environment, applicationId, { tree: container.schema, layoutId: layoutId ?? current.layoutId ?? current.layout?.id })];
|
|
899
|
+
: [...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ı)')];
|
|
1144
900
|
const summary = {
|
|
1145
901
|
id: pageId,
|
|
1146
902
|
outcome: 'updated',
|
|
1147
903
|
changed,
|
|
1148
|
-
preserved: { schemaNodes: countSchemaNodes(body.schema), events: body.events.length }
|
|
1149
|
-
...applicationTypeField(resolved)
|
|
904
|
+
preserved: { schemaNodes: countSchemaNodes(body.schema), events: body.events.length }
|
|
1150
905
|
};
|
|
1151
906
|
if (updateWarnings.length > 0)
|
|
1152
907
|
summary.layoutWarnings = updateWarnings;
|
|
1153
|
-
return withNormalization(summary, schema === undefined ? [] : [...container.fixes, ...
|
|
908
|
+
return withNormalization(summary, schema === undefined ? [] : [...container.fixes, ...reset, ...componentDefaultsNote(fixed), ...catalogNotes, ...reorderNote(moved)]);
|
|
1154
909
|
}
|
|
1155
|
-
if (templateRoot && layoutId === undefined)
|
|
1156
|
-
skeletonNotes = shellLessPageSkeleton(templateRoot);
|
|
1157
910
|
const response = await platform.tenant('POST', project, `/pages/${applicationId}`, {
|
|
1158
911
|
environment,
|
|
1159
912
|
body: {
|
|
@@ -1164,8 +917,6 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
1164
917
|
layoutId: layoutId ?? null,
|
|
1165
918
|
roleIds: roleIds ?? [],
|
|
1166
919
|
middlewareIds: middlewareIds ?? [],
|
|
1167
|
-
isAnonymous: isAnonymous ?? false,
|
|
1168
|
-
isLoginPage: isLoginPage ?? false,
|
|
1169
920
|
events: events ?? [],
|
|
1170
921
|
schema: container.schema
|
|
1171
922
|
}
|
|
@@ -1173,8 +924,6 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
1173
924
|
const saved = (response && typeof response === 'object' && !Array.isArray(response)
|
|
1174
925
|
? (response.data ?? response)
|
|
1175
926
|
: {});
|
|
1176
|
-
if (typeof saved.id === 'string')
|
|
1177
|
-
rememberVersion(project, environment, saved.id, saved.version);
|
|
1178
927
|
// Kırpmadan ÖNCE doğrulama yolu (B-55): yanıt gövdesi iskelete inince "gönderdiğim prop
|
|
1179
928
|
// kaydedildi mi" sorusunu gövde değil bu diff cevaplar. `layout_create`'in yaptığının sayfa
|
|
1180
929
|
// ikizi; Prop sarmalı açılmazsa doğru kaydedilmiş her prop düşmüş görünür (G-28).
|
|
@@ -1184,20 +933,15 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
1184
933
|
if (view !== 'full' && Array.isArray(saved.schema)) {
|
|
1185
934
|
saved.schema = saved.schema.map(node => summarize(node));
|
|
1186
935
|
}
|
|
1187
|
-
const normalized = withWriteDiff(withNormalization(withOutcome(response, undefined), [...container.fixes, ...
|
|
1188
|
-
const createWarnings = [...gaps, ...stringStyleWarning(stringStyles), ...
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
if (!normalized || typeof normalized !== 'object')
|
|
1193
|
-
return attachFindings(normalized, findings);
|
|
1194
|
-
if (createWarnings.length === 0)
|
|
1195
|
-
return attachFindings(Object.keys(typeField).length === 0 ? normalized : { ...normalized, ...typeField }, findings);
|
|
1196
|
-
return attachFindings({ ...normalized, ...typeField, layoutWarnings: createWarnings }, findings);
|
|
936
|
+
const normalized = withWriteDiff(withNormalization(withOutcome(response, undefined), [...container.fixes, ...reset, ...componentDefaultsNote(fixed), ...catalogNotes, ...reorderNote(moved)]), drops);
|
|
937
|
+
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ı)')];
|
|
938
|
+
if (createWarnings.length === 0 || !normalized || typeof normalized !== 'object')
|
|
939
|
+
return normalized;
|
|
940
|
+
return { ...normalized, layoutWarnings: createWarnings };
|
|
1197
941
|
}));
|
|
1198
942
|
server.registerTool('page_update', {
|
|
1199
943
|
title: 'Sayfa üstbilgisini güncelle',
|
|
1200
|
-
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).
|
|
944
|
+
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).",
|
|
1201
945
|
inputSchema: {
|
|
1202
946
|
project: projectParam,
|
|
1203
947
|
environment: environmentParam,
|
|
@@ -1210,74 +954,31 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
1210
954
|
layoutId: guid.nullable().optional().describe('null: layout bağını kaldırır'),
|
|
1211
955
|
roleIds: z.array(guid).optional(),
|
|
1212
956
|
middlewareIds: z.array(guid).optional(),
|
|
1213
|
-
isAnonymous: z.boolean().optional().describe('Anonim uç: true ise sayfa kompleks projede oturumsuz da açılır (erişim kapısı atlanır). Giriş ekranı ve genel açılış sayfaları için; veri gösteren sayfalarda açmayın.'),
|
|
1214
|
-
isLoginPage: z.boolean().optional().describe("Uygulamanın giriş sayfası. Bir application'da yalnız bir sayfa bu bayrağı taşımalıdır; sayfa reddi (403) bu sayfaya yönlendirilir. Giriş sayfası oturumsuz açılabilmelidir, bu yüzden isAnonymous ile birlikte kullanın."),
|
|
1215
957
|
events: eventsParam.optional(),
|
|
1216
|
-
schema: schemaParam.optional().describe('Nadiren gerekir; verilmezse mevcut şema korunur
|
|
1217
|
-
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-041 ile reddedilir (kırpık okumanın geri yazılması)'),
|
|
1218
|
-
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"),
|
|
1219
|
-
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")
|
|
958
|
+
schema: schemaParam.optional().describe('Nadiren gerekir; verilmezse mevcut şema korunur')
|
|
1220
959
|
},
|
|
1221
960
|
annotations: updates
|
|
1222
|
-
}, handle(config, async ({ project, environment, applicationId, pageId,
|
|
1223
|
-
const knownVersion = baseVersion ?? schemaReadMemory.lastVersion(pageKey(project, environment, pageId));
|
|
1224
|
-
if (knownVersion === undefined && force !== true) {
|
|
1225
|
-
throw new PlatformApiError("PAGE-043 — 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);
|
|
1226
|
-
}
|
|
961
|
+
}, handle(config, async ({ project, environment, applicationId, pageId, ...changes }) => {
|
|
1227
962
|
const overrides = Object.fromEntries(Object.entries(changes).filter(([, value]) => value !== undefined));
|
|
1228
|
-
|
|
1229
|
-
assertScreenWriteGuards('page', { schema: overrides.schema, events: overrides.events }, resolved?.profile.platform);
|
|
963
|
+
assertScreenWriteGuards('page', { schema: overrides.schema, events: overrides.events });
|
|
1230
964
|
const current = await fetchHost(platform, 'page', project, environment, applicationId, pageId);
|
|
1231
|
-
if (overrides.schema !== undefined && allowNodeLoss !== true) {
|
|
1232
|
-
const storedSchema = current.schema;
|
|
1233
|
-
const sentSchema = overrides.schema;
|
|
1234
|
-
const stored = countSchemaNodes(storedSchema);
|
|
1235
|
-
const sent = countSchemaNodes(sentSchema);
|
|
1236
|
-
if (sent < stored) {
|
|
1237
|
-
const missing = missingElementIds(storedSchema, sentSchema);
|
|
1238
|
-
const listed = missing.slice(0, 10).join(', ');
|
|
1239
|
-
const more = missing.length > 10 ? ` (+${missing.length - 10} daha)` : '';
|
|
1240
|
-
throw new PlatformApiError(`PAGE-041 — 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);
|
|
1241
|
-
}
|
|
1242
|
-
}
|
|
1243
|
-
const blocksMeaningful = resolved?.profile.layout.blocksMeaningful ?? true;
|
|
1244
965
|
const fixed = normalizeComponentDefaults(overrides.schema);
|
|
1245
|
-
const reset = overrides.schema === undefined
|
|
1246
|
-
const catalogNotes = await
|
|
1247
|
-
const moved = overrides.schema === undefined
|
|
966
|
+
const reset = overrides.schema === undefined ? [] : resetPageContainerPosition(overrides.schema);
|
|
967
|
+
const catalogNotes = await screenCatalogNotes(platform, project, environment, applicationId, overrides.schema);
|
|
968
|
+
const moved = overrides.schema === undefined ? [] : reorderSchemaBlocks(overrides.schema);
|
|
1248
969
|
const gaps = overrides.schema === undefined
|
|
1249
970
|
? []
|
|
1250
|
-
: [
|
|
1251
|
-
...(blocksMeaningful ? verticalGapWarnings(overrides.schema, 'PAGE-010 (uyarı)') : []),
|
|
1252
|
-
...(resolved?.profile.layout.modes.has('grid') ?? true ? inertFlexPropWarning(inertFlexPropNodes(overrides.schema)) : []),
|
|
1253
|
-
...flexSizeWarnings(overrides.schema, 'PAGE-017 (uyarı)', 'PAGE-018 (uyarı)'),
|
|
1254
|
-
...spacingLayerWarnings(overrides.schema, 'PAGE-029 (uyarı)', 'PAGE-030 (uyarı)'),
|
|
1255
|
-
...(resolved ? await screenProfileWarnings(platform, resolved, project, environment, applicationId, { kind: 'page', nodes: overrides.schema }) : [])
|
|
1256
|
-
];
|
|
971
|
+
: [...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ı)')];
|
|
1257
972
|
const body = pageUpdateBody(current, overrides);
|
|
1258
|
-
|
|
1259
|
-
// çıkınca da denetlenir (kabuksuz iskeletteki üst pay, payı veren kabukta çift olur).
|
|
1260
|
-
const skeletonProfile = resolved ?? (overrides.layoutId !== undefined ? await resolveProfile(platform, applicationId) : undefined);
|
|
1261
|
-
const skeletonWarnings = skeletonProfile
|
|
1262
|
-
? await pageLayoutWarnings(platform, skeletonProfile, project, environment, applicationId, { tree: body.schema, layoutId: body.layoutId })
|
|
1263
|
-
: [];
|
|
1264
|
-
if (force !== true)
|
|
1265
|
-
body.baseVersion = knownVersion;
|
|
1266
|
-
const written = await platform.tenant('PUT', project, `/pages/${applicationId}/${pageId}`, { environment, body }).catch(async (error) => {
|
|
1267
|
-
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.");
|
|
1268
|
-
});
|
|
1269
|
-
rememberVersion(project, environment, pageId, written?.data?.version);
|
|
973
|
+
await platform.tenant('PUT', project, `/pages/${applicationId}/${pageId}`, { environment, body });
|
|
1270
974
|
const summary = {
|
|
1271
975
|
id: pageId,
|
|
1272
976
|
changed: Object.keys(overrides),
|
|
1273
|
-
preserved: { schemaNodes:
|
|
977
|
+
preserved: { schemaNodes: body.schema.length, events: body.events.length }
|
|
1274
978
|
};
|
|
1275
|
-
if (
|
|
1276
|
-
summary.
|
|
1277
|
-
|
|
1278
|
-
summary.layoutWarnings = [...gaps, ...skeletonWarnings];
|
|
1279
|
-
const findings = await legacyRoleListFindings(platform, project, environment, overrides, 'page');
|
|
1280
|
-
return attachFindings(withNormalization(summary, [...reset, ...componentDefaultsNote(fixed), ...catalogNotes, ...reorderNote(moved)]), findings);
|
|
979
|
+
if (gaps.length > 0)
|
|
980
|
+
summary.layoutWarnings = gaps;
|
|
981
|
+
return withNormalization(summary, [...reset, ...componentDefaultsNote(fixed), ...catalogNotes, ...reorderNote(moved)]);
|
|
1281
982
|
}));
|
|
1282
983
|
server.registerTool('page_clone', {
|
|
1283
984
|
title: 'Sayfa klonla',
|
|
@@ -1285,26 +986,6 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
1285
986
|
inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid, pageId: guid },
|
|
1286
987
|
annotations: writes
|
|
1287
988
|
}, handle(config, ({ project, environment, applicationId, pageId }) => platform.tenant('POST', project, `/pages/${applicationId}/clone/${pageId}`, { environment })));
|
|
1288
|
-
server.registerTool('page_restore', {
|
|
1289
|
-
title: 'Sayfayı bir revizyona döndür',
|
|
1290
|
-
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.",
|
|
1291
|
-
inputSchema: {
|
|
1292
|
-
project: projectParam,
|
|
1293
|
-
environment: environmentParam,
|
|
1294
|
-
applicationId: guid,
|
|
1295
|
-
pageId: guid,
|
|
1296
|
-
revisionNo: z.number().int().min(0).describe("Dönülecek sayfa revizyonunun numarası (revision_list revisionNo)"),
|
|
1297
|
-
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'),
|
|
1298
|
-
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')
|
|
1299
|
-
},
|
|
1300
|
-
annotations: destructive
|
|
1301
|
-
}, handle(config, async ({ project, environment, applicationId, pageId, revisionNo, baseVersion, allowEmpty }) => {
|
|
1302
|
-
const restored = unwrap(await platform.tenant('POST', project, `/pages/${applicationId}/${pageId}/restore`, {
|
|
1303
|
-
environment,
|
|
1304
|
-
body: { revisionNo, ...(baseVersion ? { baseVersion } : {}), ...(allowEmpty ? { allowEmpty: true } : {}) }
|
|
1305
|
-
}));
|
|
1306
|
-
return { pageId, ...restored };
|
|
1307
|
-
}));
|
|
1308
989
|
server.registerTool('page_delete', {
|
|
1309
990
|
title: 'Sayfa sil',
|
|
1310
991
|
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.',
|
|
@@ -1319,37 +1000,3 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
|
|
|
1319
1000
|
return { deleted: pageId, name: page.name };
|
|
1320
1001
|
}));
|
|
1321
1002
|
}
|
|
1322
|
-
/**
|
|
1323
|
-
* Mobil yazımda yeni düğümlerin boşluk varsayılanı (mizanpaj S5/S6); kardeşler kayıtlı ağaçtan okunur.
|
|
1324
|
-
* Ağaç okunamazsa dokunulmaz: eski bir kaba eklenen düğümü yanlışlıkla kaydırmaktansa varsayılan kalsın.
|
|
1325
|
-
*/
|
|
1326
|
-
async function mobileSpacingForOps(platformName, ops, readSaved) {
|
|
1327
|
-
const fix = emptyMobileSpacingFix();
|
|
1328
|
-
if (platformName !== 'mobile' || !ops.some(entry => entry.op === 'add_component' || entry.op === 'batch'))
|
|
1329
|
-
return fix;
|
|
1330
|
-
const saved = await readSaved().then(page => page.schema, () => undefined);
|
|
1331
|
-
if (saved !== undefined)
|
|
1332
|
-
applyMobileSpacingToOps(ops, saved, fix);
|
|
1333
|
-
return fix;
|
|
1334
|
-
}
|
|
1335
|
-
/**
|
|
1336
|
-
* Yazımdan sonraki sayfa ağacının mizanpaj iskeleti (MOB-015…018). `audit` (kuru koşu) ağacın tamamını,
|
|
1337
|
-
* `new` (yama) yalnız yazımın getirdiği bulguları söyler — yoksa bozuk bir sayfaya yapılan her yama aynı
|
|
1338
|
-
* uyarıları tekrarlardı. Kayıtlı sayfa okunamazsa denetim koşmaz.
|
|
1339
|
-
*/
|
|
1340
|
-
async function simulatedPageLayoutWarnings(platform, resolved, project, environment, applicationId, readSaved, ops, mode) {
|
|
1341
|
-
if (resolved.profile.platform !== 'mobile')
|
|
1342
|
-
return [];
|
|
1343
|
-
const page = await readSaved().catch(() => undefined);
|
|
1344
|
-
if (!page)
|
|
1345
|
-
return [];
|
|
1346
|
-
const saved = page.schema ?? [];
|
|
1347
|
-
const simulated = JSON.parse(JSON.stringify(saved));
|
|
1348
|
-
simulateLayoutOps(simulated, ops);
|
|
1349
|
-
applySkeletonOps(simulated, ops);
|
|
1350
|
-
return pageLayoutWarnings(platform, resolved, project, environment, applicationId, {
|
|
1351
|
-
tree: simulated,
|
|
1352
|
-
...(mode === 'new' ? { before: saved } : {}),
|
|
1353
|
-
layoutId: page.layoutId ?? page.layout?.id
|
|
1354
|
-
});
|
|
1355
|
-
}
|