@appaflytech/zeyrek-ai-mcp 0.0.6 → 0.0.8
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 +2 -2
- package/.env.production.example +8 -0
- package/README.md +9 -8
- package/dist/config.js +18 -3
- package/dist/knowledge/backendConstraints.js +88 -0
- package/dist/knowledge/componentCatalog.js +415 -68
- package/dist/knowledge/componentEvents.js +120 -25
- package/dist/knowledge/functionGuards.js +134 -0
- package/dist/knowledge/normalize.js +512 -25
- package/dist/knowledge/outlines.js +31 -0
- package/dist/knowledge/policy.js +14 -5
- package/dist/knowledge/propTypes.js +5 -4
- package/dist/knowledge/rules.js +453 -6
- package/dist/knowledge/styleResolve.js +6 -1
- package/dist/platform/csv.js +123 -0
- package/dist/platform/errors.js +62 -3
- package/dist/platform/http.js +11 -2
- package/dist/platform/responses.js +16 -4
- package/dist/platform/screenshot.js +98 -9
- package/dist/platform/strictArguments.js +32 -0
- package/dist/platform/writeDiff.js +6 -3
- package/dist/resources/examples/flow-call-function.json +66 -0
- package/dist/resources/examples/function-total.json +70 -0
- package/dist/resources/examples/microfunction-node-sum.json +56 -0
- package/dist/resources/examples/page-dynamic-form.json +1 -1
- package/dist/resources/examples/page-sign-in.json +2 -2
- package/dist/resources/guides/component.md +14 -2
- package/dist/resources/guides/entity.md +321 -17
- package/dist/resources/guides/expressions.md +9 -0
- package/dist/resources/guides/functions.md +262 -0
- package/dist/resources/guides/getting-started.md +7 -0
- package/dist/resources/guides/layout.md +6 -0
- package/dist/resources/guides/merge.md +231 -0
- package/dist/resources/guides/microfunction.md +166 -0
- package/dist/resources/guides/microservice.md +166 -7
- package/dist/resources/guides/modify-existing.md +23 -0
- package/dist/resources/guides/page.md +101 -22
- package/dist/resources/guides/translation.md +35 -10
- package/dist/resources/guides/workflow.md +51 -2
- package/dist/resources/schema-fingerprint.json +4 -4
- package/dist/resources/schemas/AnalyzeImportQuery.json +51 -0
- package/dist/resources/schemas/ApplyMergePlanCommand.json +19 -0
- package/dist/resources/schemas/CreateBulkRowCommand.json +3 -0
- package/dist/resources/schemas/CreateEnvironmentCommand.json +31 -0
- package/dist/resources/schemas/CreateGlobalFunctionCommand.json +95 -0
- package/dist/resources/schemas/CreateImportPlanCommand.json +278 -0
- package/dist/resources/schemas/CreateMergePlanCommand.json +34 -0
- package/dist/resources/schemas/CreateMicrofunctionCommand.json +117 -0
- package/dist/resources/schemas/CreateMicrofunctionRuntimeCommand.json +51 -0
- package/dist/resources/schemas/CreateMicroserviceCommand.json +123 -2
- package/dist/resources/schemas/CreateWorkflowCommand.json +17 -3
- package/dist/resources/schemas/DataJobDto.json +267 -0
- package/dist/resources/schemas/ExportRowsCommand.json +172 -0
- package/dist/resources/schemas/Filter.json +3 -1
- package/dist/resources/schemas/GetRowsQuery.json +3 -1
- package/dist/resources/schemas/GlobalFunctionDto.json +89 -0
- package/dist/resources/schemas/GlobalFunctionUpdateResultDto.json +249 -0
- package/dist/resources/schemas/ImportAnalysisDto.json +401 -0
- package/dist/resources/schemas/ImportPlanDto.json +260 -0
- package/dist/resources/schemas/ImportSampleDto.json +90 -0
- package/dist/resources/schemas/InvokeMicrofunctionCommand.json +25 -0
- package/dist/resources/schemas/PreviewMicroserviceCommand.json +123 -2
- package/dist/resources/schemas/PreviewWorkflowCommand.json +17 -3
- package/dist/resources/schemas/ResolveMergeConflictsCommand.json +58 -0
- package/dist/resources/schemas/RevertMergePlanCommand.json +19 -0
- package/dist/resources/schemas/RunImportCommand.json +62 -0
- package/dist/resources/schemas/StepDto.json +17 -3
- package/dist/resources/schemas/UpdateBulkRowCommand.json +3 -0
- package/dist/resources/schemas/UpdateEntityCommand.json +3 -1
- package/dist/resources/schemas/UpdateGlobalFunctionCommand.json +66 -0
- package/dist/resources/schemas/UpdateMicrofunctionCommand.json +121 -0
- package/dist/resources/schemas/UpdateMicrofunctionRuntimeCommand.json +54 -0
- package/dist/resources/schemas/UpdateMicroserviceCommand.json +123 -2
- package/dist/resources/schemas/UpdateWorkflowCommand.json +17 -3
- package/dist/server.js +17 -4
- package/dist/tools/activities.js +40 -0
- package/dist/tools/automations.js +14 -5
- package/dist/tools/common.js +16 -0
- package/dist/tools/dependencies.js +1 -1
- package/dist/tools/environments.js +52 -0
- package/dist/tools/files.js +51 -50
- package/dist/tools/flows.js +104 -5
- package/dist/tools/functions.js +331 -0
- package/dist/tools/importSchemas.js +79 -0
- package/dist/tools/imports.js +241 -0
- package/dist/tools/layouts.js +4 -4
- package/dist/tools/merges.js +113 -0
- package/dist/tools/microfunctions.js +155 -0
- package/dist/tools/microservices.js +112 -9
- package/dist/tools/pages.js +188 -66
- package/dist/tools/platformInfo.js +4 -1
- package/dist/tools/queries.js +72 -6
- package/dist/tools/revisions.js +94 -0
- package/dist/tools/rows.js +342 -22
- package/dist/tools/screenWrite.js +11 -2
- package/dist/tools/templates.js +32 -5
- package/dist/tools/workflows.js +28 -6
- package/package.json +3 -1
package/dist/tools/pages.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { guid, projectParam, environmentParam, pagingParams, readOnly, writes, updates, destructive, microOps } from './common.js';
|
|
2
|
-
import { orphanedFlowWarnings, orphanedFlowPlan, orphanedFlowMessage, staleSetterWarnings, normalizeComponentDefaults, normalizeSchemaOps, schemaOpValidationErrors, validationNote, componentDefaultsNote, replacePropNote, ensurePageContainer, retargetRootAdds, rootAddsNote, flexSizeWarnings, reorderSchemaBlocks, reorderNote, nestedRepeaterNodes, nestedRepeaterMessage, stringStyleNodes, stringStyleWarning, inertFlexPropNodes, inertFlexPropWarning, isFlexContainer, FLEX_LAYOUT_PROP_NAMES, resetPageContainerPosition, pageContainerPositionWarning, verticalGapWarnings, withNormalization, PAGE_CONTAINER_ELEMENT_ID } from '../knowledge/normalize.js';
|
|
1
|
+
import { guid, projectParam, environmentParam, pagingParams, readOnly, writes, updates, destructive, microOps, isBranchName } from './common.js';
|
|
2
|
+
import { orphanedFlowWarnings, orphanedFlowPlan, orphanedFlowMessage, staleSetterWarnings, lostSetterTargets, normalizeComponentDefaults, normalizeSchemaOps, schemaOpValidationErrors, codePropTemplateErrors, validationNote, componentDefaultsNote, replacePropNote, ensurePageContainer, retargetRootAdds, rootAddsNote, flexSizeWarnings, spacingLayerWarnings, reorderSchemaBlocks, reorderNote, nestedRepeaterNodes, nestedRepeaterMessage, stringStyleNodes, stringStyleWarning, inertFlexPropNodes, inertFlexPropWarning, isFlexContainer, FLEX_LAYOUT_PROP_NAMES, resetPageContainerPosition, pageContainerPositionWarning, verticalGapWarnings, flexIndexOrder, flexIndexOrderNote, withNormalization, parseVariableEntries, PAGE_CONTAINER_ELEMENT_ID } from '../knowledge/normalize.js';
|
|
3
|
+
import { functionSignature } from '../knowledge/functionGuards.js';
|
|
3
4
|
import { fetchHost, pageUpdateBody, schemaParam, eventsParam, unwrap, modeParam, summarize, outlineAppliedPatch, findNode, flowPath } from './screens.js';
|
|
4
5
|
import { fetchComponentEventNames } from './components.js';
|
|
5
6
|
import { assertScreenWriteGuards, screenCatalogNotes } from './screenWrite.js';
|
|
@@ -9,11 +10,31 @@ import { PlatformApiError } from '../platform/errors.js';
|
|
|
9
10
|
import { handle, toolResult, toolError } from '../platform/responses.js';
|
|
10
11
|
import { StudioScreenshotter } from '../platform/screenshot.js';
|
|
11
12
|
import { summarizeLayout } from '../knowledge/layoutSummary.js';
|
|
13
|
+
import { normalizeLanguageCode } from '../knowledge/languageCodes.js';
|
|
14
|
+
import { readProjectLanguages } from '../knowledge/languages.js';
|
|
12
15
|
import { resolveIdentityToken } from '../platform/tokenVault.js';
|
|
13
16
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
14
17
|
import { dirname, resolve } from 'node:path';
|
|
15
18
|
import { applyPropTypesToSchema, setPropTypeErrors } from '../knowledge/propTypes.js';
|
|
16
19
|
import { z } from 'zod';
|
|
20
|
+
/**
|
|
21
|
+
* Runtime paketindeki `appFunctions`/`pageFunctions` (UiFunctionRuntimeDto: id, name, parameters,
|
|
22
|
+
* body, isAsync) — imza taşınır, GÖVDE TAŞINMAZ (MAX_RESPONSE_BYTES). Ajan Functions.<ad>(…)
|
|
23
|
+
* yazarken adı ve parametre sırasını buradan alır (G-06).
|
|
24
|
+
*/
|
|
25
|
+
function runtimeFunctions(value) {
|
|
26
|
+
return (Array.isArray(value) ? value : [])
|
|
27
|
+
.filter((entry) => Boolean(entry) && typeof entry === 'object')
|
|
28
|
+
.map(entry => ({
|
|
29
|
+
id: entry.id,
|
|
30
|
+
name: entry.name,
|
|
31
|
+
signature: functionSignature(entry),
|
|
32
|
+
isAsync: entry.isAsync === true,
|
|
33
|
+
parameters: (Array.isArray(entry.parameters) ? entry.parameters : [])
|
|
34
|
+
.filter((parameter) => Boolean(parameter) && typeof parameter === 'object')
|
|
35
|
+
.map(parameter => ({ name: parameter.name, required: parameter.required === true, defaultValue: parameter.defaultValue ?? null }))
|
|
36
|
+
}));
|
|
37
|
+
}
|
|
17
38
|
/**
|
|
18
39
|
* Yazma yanıtındaki şema gövdesinin biçimi (B-55 · S-9). Varsayılan `outline`: gövde ajanın
|
|
19
40
|
* aynı turda kendi gönderdiği içeriğin yankısıdır, kanıt ise pointer'da ve `silentlyDropped`ta
|
|
@@ -30,7 +51,7 @@ function outlineWriteResponse(response) {
|
|
|
30
51
|
if (Array.isArray(container.appliedPatch))
|
|
31
52
|
container.appliedPatch = outlineAppliedPatch(container.appliedPatch);
|
|
32
53
|
}
|
|
33
|
-
const opsParam = z.array(z.record(z.unknown())).describe("Micro-op listesi. Op sözlüğü: replace_prop {target, prop, value} | set_binding {target, prop, expression: '{{ Page.variables.x }}'} | add_component {parent (üst elementId ya da 'root'), index?, component} | remove_component {target} | move_component {target, parent, index?, position: {x, y}} | set_event {target, event, eventValue} | remove_event {target, event} (gateway emülasyonu: platformun micro-op'u olay SİLEMEZ, eventValue: null yalnız değersiz ölü kayıt bırakır — bu op şemayı okuyup olayı diziden çıkarır ve sayfayı tam gövdeyle geri yazar) | set_validations {target, validations: [{type, message, value, isActive}]} (komponentin validation listesini TAMAMEN değiştirir; boş dizi hepsini siler) | resize_component {target, size?: {w,h}, appearance?: {widthType,widthString,heightType,heightString}, states?: {isWrapper,isScrollable,isSticky,isDroppable,isFloating,isQuickStack}, breakpoint?: 1024} (gateway emülasyonu: platformun micro-op'unda boyut YOKTUR ve move_component'e konan size sessizce düşer — bu op şemayı okuyup hedefin boyutunu değiştirir ve sayfayı tam gövdeyle geri yazar. Izgarada blocks[].size, flex'te appearance geçerlidir; ebeveynin düzeni yazılanı etkisiz kılıyorsa PAGE-019 uyarısı çıkar. states bir PROP DEĞİL düğüm alanıdır ve replace_prop onu yazamaz — kurulmuş bir düğümün isWrapper bayrağını değiştirmenin tek yolu buydu). set_prop_type {target, prop, propType: 'static'|'code'|'translation', ref?, data?} (gateway emülasyonu: replace_prop
|
|
54
|
+
const opsParam = z.array(z.record(z.unknown())).describe("Micro-op listesi. Op sözlüğü: replace_prop {target, prop, value} (value ham değer, code sarmalı {type: 'code', ref: '{{ … }}'} ya da translation sarmalı {type: 'translation', ref: '<çeviri anahtarı>'} — prop tipini değiştirmek için komponenti silip yeniden EKLEMEYİN, ona yazan setter'ların hedefi silinir (FLOW-018); platform replace_prop'ta expression alanını OKUMAZ — gateway onu code sarmalına çevirir) | set_binding {target, prop, expression: '{{ Page.variables.x }}'} | add_component {parent (üst elementId ya da 'root'), index?, component} | remove_component {target} | move_component {target, parent, index?, position: {x, y}} (index ÇİZİM sırası değildir, runtime çocukları blocks.position'a göre (y, sonra x) çizer: flex kapta gateway index'i görünen sıraya çevirir ve kardeş konumlarını yeniden yazar; ızgara kapta sırayı position.y verir, sona eklemeyen index PAGE-033 uyarısıdır) | set_event {target, event, eventValue} | remove_event {target, event} (gateway emülasyonu: platformun micro-op'u olay SİLEMEZ, eventValue: null yalnız değersiz ölü kayıt bırakır — bu op şemayı okuyup olayı diziden çıkarır ve sayfayı tam gövdeyle geri yazar) | set_validations {target, validations: [{type, message, value, isActive}]} (komponentin validation listesini TAMAMEN değiştirir; boş dizi hepsini siler) | set_appearance {target, appearance: {heightType?, widthType?, tooltip?, background?, …}} (kurulu düğümün appearance'ına YALNIZ verilen alanları yazar, gerisini korur; null alanı temizler; bilinmeyen alan reddedilir. GÖRÜNÜRLÜK appearance'ta DEĞİLDİR: gizlemek için replace_prop {target, prop: 'isHidden', value: true}, koşul için set_binding {prop: 'isHidden'} — add_component gövdesindeki appearance.isHidden'ı gateway props.isHidden'a taşır) | resize_component {target, size?: {w,h}, appearance?: {widthType,widthString,heightType,heightString}, states?: {isWrapper,isScrollable,isSticky,isDroppable,isFloating,isQuickStack}, breakpoint?: 1024} (gateway emülasyonu: platformun micro-op'unda boyut YOKTUR ve move_component'e konan size sessizce düşer — bu op şemayı okuyup hedefin boyutunu değiştirir ve sayfayı tam gövdeyle geri yazar. Izgarada blocks[].size, flex'te appearance geçerlidir; ebeveynin düzeni yazılanı etkisiz kılıyorsa PAGE-019 uyarısı çıkar. states bir PROP DEĞİL düğüm alanıdır ve replace_prop onu yazamaz — kurulmuş bir düğümün isWrapper bayrağını değiştirmenin tek yolu buydu). set_prop_type {target, prop, propType: 'static'|'code'|'translation', ref?, data?} (gateway emülasyonu: replace_prop sarmalıyla aynı sonucu verir ve mevcut data'yı tasarım yedeği olarak taşır. Bu op şemayı okuyup prop'un TİPİNİ yerinde değiştirir ve sayfayı tam gövdeyle geri yazar. Taşıyıcı alan tipe göre: static → data, code ve translation → ref (runtime data'yı her turda ezer, anahtar yalnız ref'te yaşar). Eksik taşıyıcı istek gönderilmeden reddedilir; page_schema_validate aynı kararı verir). target = elementId.");
|
|
34
55
|
/**
|
|
35
56
|
* PAGE-008 — `set_event` uydurma bir olay adıyla 200 döner ve bağ SESSİZCE ölü kalır: motor olayı
|
|
36
57
|
* komponentin yaydığı adla eşleştirir, eşleşme yoksa akış hiç çalışmaz. Kontrol yalnız hedefin
|
|
@@ -51,6 +72,20 @@ function unknownEventOps(ops, typesByElementId, eventsBySlug) {
|
|
|
51
72
|
return [`'${target}' (${type}) komponenti '${event}' diye bir olay YAYMAZ. Yaydıkları: ${declared.join(', ')}. Platform bu op'u 200 ile kabul eder ama bağ sessizce ölü kalır — akış hiçbir zaman çalışmaz. Doğru adı component_get(slug:'${type}') çıktısındaki events listesinden alın.`];
|
|
52
73
|
});
|
|
53
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Ön kontroller eskiden İLK hatada fırlatıyordu: dört op'u yanlış olan bir yama dört ayrı turda
|
|
77
|
+
* ancak görülüyordu ve her tur tüm planı yeniden gönderiyordu (S-11, 2026-09-15). Artık op başına
|
|
78
|
+
* toplanır ve tek yanıtta döner. Yama yine ATOMİKTİR — birbirine bağlı op'lar (add_component +
|
|
79
|
+
* set_event) yarım kalmasın diye geçenler de gönderilmez.
|
|
80
|
+
*/
|
|
81
|
+
function rejectedOpsMessage(unordered) {
|
|
82
|
+
const rejected = [...unordered].sort((left, right) => left.index - right.index);
|
|
83
|
+
const lines = rejected.map(entry => `#${entry.index} ${entry.op}${entry.target ? ` '${entry.target}'` : ''} [${entry.code}]: ${entry.message}`);
|
|
84
|
+
const validationHint = rejected.some(entry => entry.code === 'PAGE-016')
|
|
85
|
+
? " PAGE-016 çalışan biçimi dört alanı da taşır: { type: 'required'|'email'|'regex'|'max-length'|'min-length'|'max-value'|'min-value'|'value-options', message: '<kullanıcıya görünen metin>', value: <eşik/desen/true>, isActive: true }."
|
|
86
|
+
: '';
|
|
87
|
+
return `Ön kontrol ${rejected.length} op'u reddetti; yama atomik olduğu için HİÇBİR op gönderilmedi (istek gönderilmedi). Düşen op'ları düzeltip TÜM listeyi tek çağrıda yeniden gönderin.${validationHint}\n${lines.join('\n')}\nrejectedOps: ${JSON.stringify(rejected)}`;
|
|
88
|
+
}
|
|
54
89
|
function collectTypes(nodes, into) {
|
|
55
90
|
for (const node of nodes ?? []) {
|
|
56
91
|
if (typeof node.elementId === 'string' && typeof node.type === 'string')
|
|
@@ -58,11 +93,11 @@ function collectTypes(nodes, into) {
|
|
|
58
93
|
collectTypes(node.children, into);
|
|
59
94
|
}
|
|
60
95
|
}
|
|
61
|
-
function removeNodeFromTree(nodes, target) {
|
|
96
|
+
function removeNodeFromTree(nodes, target, parent) {
|
|
62
97
|
for (let index = 0; index < nodes.length; index++) {
|
|
63
98
|
if (nodes[index].elementId === target)
|
|
64
|
-
return nodes.splice(index, 1)[0];
|
|
65
|
-
const found = removeNodeFromTree(nodes[index].children ?? [], target);
|
|
99
|
+
return { node: nodes.splice(index, 1)[0], parent };
|
|
100
|
+
const found = removeNodeFromTree(nodes[index].children ?? [], target, nodes[index]);
|
|
66
101
|
if (found)
|
|
67
102
|
return found;
|
|
68
103
|
}
|
|
@@ -78,48 +113,67 @@ function setDesktopPosition(node, position) {
|
|
|
78
113
|
* PAGE-010 boşluk analizi patch'in SONUCUNU görmek zorunda: tek başına op'lardaki y değerleri,
|
|
79
114
|
* kapsayıcının mevcut çocukları bilinmeden anlamsız. Op'lar okunan şemanın kopyasına uygulanır
|
|
80
115
|
* (add: parent'ın children'ına ekle — kökler retargetRootAdds ile page_container'a gittiği için
|
|
81
|
-
* burada da öyle çözülür; move: düğümü taşı
|
|
116
|
+
* burada da öyle çözülür; move: düğümü taşı — parent verilmemişse aynı ebeveynde kalır — ve verilen
|
|
117
|
+
* position'ı desktop block'una yaz; ikisinde de index platformdaki gibi diziye yerleşir) ve analiz
|
|
82
118
|
* yalnız dokunulan kapsayıcılarda koşar.
|
|
83
119
|
*/
|
|
84
120
|
function simulateLayoutOps(schema, ops) {
|
|
85
121
|
const touched = new Set();
|
|
86
|
-
const attach = (node, requestedParent) => {
|
|
122
|
+
const attach = (node, requestedParent, index) => {
|
|
87
123
|
const parentId = typeof requestedParent === 'string' && requestedParent !== '' && requestedParent !== 'root'
|
|
88
124
|
? requestedParent
|
|
89
125
|
: PAGE_CONTAINER_ELEMENT_ID;
|
|
90
126
|
const parent = findNode(schema, parentId);
|
|
127
|
+
const siblings = parent ? [...(parent.children ?? [])] : schema;
|
|
128
|
+
const insertAt = typeof index === 'number' && Number.isFinite(index) ? Math.max(0, Math.min(index, siblings.length)) : siblings.length;
|
|
129
|
+
siblings.splice(insertAt, 0, node);
|
|
91
130
|
if (parent) {
|
|
92
|
-
parent.children =
|
|
131
|
+
parent.children = siblings;
|
|
93
132
|
touched.add(String(parent.elementId ?? parentId));
|
|
94
133
|
}
|
|
95
134
|
else {
|
|
96
|
-
schema.push(node);
|
|
97
135
|
touched.add('kök');
|
|
98
136
|
}
|
|
99
137
|
};
|
|
100
138
|
for (const op of ops) {
|
|
101
139
|
if (op.op === 'add_component' && op.component && typeof op.component === 'object') {
|
|
102
|
-
attach(JSON.parse(JSON.stringify(op.component)), op.parent);
|
|
140
|
+
attach(JSON.parse(JSON.stringify(op.component)), op.parent, op.index);
|
|
103
141
|
}
|
|
104
142
|
// Düzen prop'ları (layout/flex grubu) simülasyona girer: PAGE-010 flex kapsayıcının içini
|
|
105
143
|
// analiz etmez, PAGE-015 ise op'tan SONRAKİ layout değerine bakar — ikisi de op uygulanmadan
|
|
106
144
|
// doğru karar veremez.
|
|
107
145
|
if (op.op === 'replace_prop' && FLEX_LAYOUT_PROP_NAMES.has(String(op.prop ?? ''))) {
|
|
108
146
|
const target = findNode(schema, String(op.target ?? ''));
|
|
147
|
+
const codeWrapper = op.value && typeof op.value === 'object' && op.value.type === 'code';
|
|
109
148
|
if (target)
|
|
110
|
-
target.props = { ...(target.props ?? {}), [String(op.prop)]: { type: 'static', ref: null, data: op.value } };
|
|
149
|
+
target.props = { ...(target.props ?? {}), [String(op.prop)]: codeWrapper ? op.value : { type: 'static', ref: null, data: op.value } };
|
|
111
150
|
}
|
|
112
151
|
if (op.op === 'move_component') {
|
|
113
152
|
const moved = removeNodeFromTree(schema, String(op.target ?? ''));
|
|
114
153
|
if (!moved)
|
|
115
154
|
continue;
|
|
116
155
|
if (op.position && typeof op.position === 'object')
|
|
117
|
-
setDesktopPosition(moved, op.position);
|
|
118
|
-
attach(moved, op.parent);
|
|
156
|
+
setDesktopPosition(moved.node, op.position);
|
|
157
|
+
attach(moved.node, op.parent ?? moved.parent?.elementId ?? 'root', op.index);
|
|
119
158
|
}
|
|
120
159
|
}
|
|
121
160
|
return touched;
|
|
122
161
|
}
|
|
162
|
+
/**
|
|
163
|
+
* G-10: `index` taşıyan add/move op'u varsa sayfa okunur ve `flexIndexOrder` flex kapta görünen
|
|
164
|
+
* sırayı konumlara çevirir. Okuma başarısız olursa op'lar olduğu gibi gider — bugünkü davranış.
|
|
165
|
+
*/
|
|
166
|
+
async function orderByIndex(ops, readPage) {
|
|
167
|
+
if (!ops.some(entry => (entry.op === 'add_component' || entry.op === 'move_component') && typeof entry.index === 'number')) {
|
|
168
|
+
return { ops, moved: [], warnings: [] };
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
return flexIndexOrder((await readPage()).schema, ops);
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
return { ops, moved: [], warnings: [] };
|
|
175
|
+
}
|
|
176
|
+
}
|
|
123
177
|
async function fetchPage(platform, project, environment, applicationId, pageId) {
|
|
124
178
|
const response = await platform.tenant('GET', project, `/pages/${applicationId}/${pageId}`, { environment });
|
|
125
179
|
return response?.data ?? {};
|
|
@@ -324,7 +378,7 @@ export function registerPageTools(server, platform, injectedScreenshotter) {
|
|
|
324
378
|
}));
|
|
325
379
|
server.registerTool('page_runtime_get', {
|
|
326
380
|
title: 'Sayfanın runtime çıktısını getir',
|
|
327
|
-
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
|
|
381
|
+
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.",
|
|
328
382
|
inputSchema: {
|
|
329
383
|
project: projectParam,
|
|
330
384
|
environment: environmentParam,
|
|
@@ -375,6 +429,10 @@ export function registerPageTools(server, platform, injectedScreenshotter) {
|
|
|
375
429
|
widgets: named(body.widgets),
|
|
376
430
|
flows: named(body.flows),
|
|
377
431
|
variableKeys: Object.keys((body.variables ?? {})),
|
|
432
|
+
pageVariableKeys: parseVariableEntries(body.pageVariables).map(entry => entry.key),
|
|
433
|
+
appVariableKeys: parseVariableEntries(body.appVariables).map(entry => entry.key),
|
|
434
|
+
appFunctions: runtimeFunctions(body.appFunctions),
|
|
435
|
+
pageFunctions: runtimeFunctions(body.pageFunctions),
|
|
378
436
|
hasTheme: Boolean(body.project?.theme),
|
|
379
437
|
note: resolveStyles
|
|
380
438
|
? "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."
|
|
@@ -385,7 +443,7 @@ export function registerPageTools(server, platform, injectedScreenshotter) {
|
|
|
385
443
|
const maxInlinePngBytes = 5 * 1024 * 1024;
|
|
386
444
|
server.registerTool('page_screenshot', {
|
|
387
445
|
title: 'Sayfanın ekran görüntüsünü al',
|
|
388
|
-
description: "Studio arayüzünün runtime render rotasını (/dev ya da /view) headless Chromium'da açar ve sayfanın PNG ekran görüntüsünü inline görüntü olarak döner — kurduğun sayfayı GÖZLE doğrulamanın yolu budur (page_runtime_get paketi verir, bu tool ekranda ne ÇİZİLDİĞİNİ gösterir). Sayfaya bağlı middleware'ler gerçekten koşar; reddedilirse yanıtta middlewareBlocked işaretlenir ve Studio'nun çizdiği hata durumu karesi döner — bu da bir doğrulamadır. YÜKLENMEMİŞ SAYFA ASLA ÇEKİLMEZ: kare, runtime çizilip mount akışları bitince alınır (yanıtta pageState + readyAfterMs); bütçe dolduğunda sayfa hâlâ iskelette ya da akış koşuyorsa çağrı ne görüldüğünü söyleyen bir hatayla döner, boş/yarım kare dönmez. Sayfa akışı bir adımda düşerse (patlayan ifade, hata dönen uç) yanıt runtimeErrors taşır ve hangi komponentin hangi prop'unun YAZILMADIĞINI söyler — kare 'çalışıyor' görünürken sessizce boş kalan komponenti ancak bu yakalar. STUDIO_BASE_URL tanımlı olmalı; ilk kullanım için `npx playwright-core install chromium` gerekir.",
|
|
446
|
+
description: "Studio arayüzünün runtime render rotasını (/dev ya da /view) headless Chromium'da açar ve sayfanın PNG ekran görüntüsünü inline görüntü olarak döner — kurduğun sayfayı GÖZLE doğrulamanın yolu budur (page_runtime_get paketi verir, bu tool ekranda ne ÇİZİLDİĞİNİ gösterir). Sayfaya bağlı middleware'ler gerçekten koşar; reddedilirse yanıtta middlewareBlocked işaretlenir ve Studio'nun çizdiği hata durumu karesi döner — bu da bir doğrulamadır. YÜKLENMEMİŞ SAYFA ASLA ÇEKİLMEZ: kare, runtime çizilip mount akışları bitince alınır (yanıtta pageState + readyAfterMs); bütçe dolduğunda sayfa hâlâ iskelette ya da akış koşuyorsa çağrı ne görüldüğünü söyleyen bir hatayla döner, boş/yarım kare dönmez. Sayfa akışı bir adımda düşerse (patlayan ifade, hata dönen uç) yanıt runtimeErrors taşır ve hangi komponentin hangi prop'unun YAZILMADIĞINI söyler — kare 'çalışıyor' görünürken sessizce boş kalan komponenti ancak bu yakalar. Kare DİLİ ve TEMASI seçilebilir: 'language' adrese ?lang ekler (çeviriye bağlı prop'lar da çok dilli alanların satır değerleri de o dilde çözülür), 'colorScheme' ziyaretçinin tema tercihini sayfa açılmadan eker — ikisi de sayfaya dil değiştirici ya da theme-toggle koymayı GEREKTİRMEZ, yani referansa sadık bir tasarımın ikinci dili ve koyu teması bozulmadan kanıtlanır. STUDIO_BASE_URL tanımlı olmalı; ilk kullanım için `npx playwright-core install chromium` gerekir. DAL ORTAMI DA GÖRÜNTÜLENİR (2026-09-09'dan beri): 'development' /dev, 'production' /view, dal ise /branch/{dal} şeridinden açılır ve sayfanın BÜTÜN istekleri (şema da akışlar da) o dala gider — dalda kurulan sayfa artık merge'i beklemeden gözle doğrulanır (platform_guide('merge') §Dalda görsel doğrulama).",
|
|
389
447
|
inputSchema: {
|
|
390
448
|
project: projectParam,
|
|
391
449
|
environment: environmentParam,
|
|
@@ -404,28 +462,55 @@ export function registerPageTools(server, platform, injectedScreenshotter) {
|
|
|
404
462
|
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."),
|
|
405
463
|
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."),
|
|
406
464
|
actions: z.array(z.object({
|
|
407
|
-
action: z.enum(['set', 'click', 'wait']),
|
|
408
|
-
target: z.string().min(1).optional().describe("Hedefin elementId'si (page_schema_get); runtime düğümü data-element-id ile bulunur"),
|
|
409
|
-
|
|
465
|
+
action: z.enum(['set', 'click', 'wait', 'drag']),
|
|
466
|
+
target: z.string().min(1).optional().describe("Hedefin elementId'si (page_schema_get); runtime düğümü data-element-id ile bulunur. 'drag' için sürüklenen öğe (ör. 'kb_board__card-42', 'cal_plan__event-7')"),
|
|
467
|
+
to: z.string().min(1).optional().describe("Yalnız 'drag': bırakma hedefinin elementId'si — kanban kolonu '<elementId>__col-<value>', takvim günü '<elementId>__day-<yyyy-MM-dd>', tüm gün hücresi '<elementId>__allday-<yyyy-MM-dd>', saat dilimi '<elementId>__slot-<yyyy-MM-dd>T<HH>'"),
|
|
468
|
+
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'"),
|
|
410
469
|
ms: z.number().int().min(0).max(15000).optional().describe("'wait' için bekleme süresi")
|
|
411
|
-
})).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'}]. 'set' native setter kullanır ve input+change olaylarını bubbles ile atar (düz DOM ataması framework'ün duyduğu olayı üretmez). 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)."),
|
|
470
|
+
})).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:'drag', target:'kb_board__card-42', to:'kb_board__col-Assigned'}]. '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)."),
|
|
471
|
+
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ı."),
|
|
472
|
+
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."),
|
|
412
473
|
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')
|
|
413
474
|
},
|
|
414
475
|
annotations: readOnly
|
|
415
|
-
}, async ({ project, environment, applicationSlug, pageSlug, viewport, fullPage, delayMs, captureUnready, anonymous, asUser, cookies, layoutReport, actions, outPath, expectFailure }) => {
|
|
476
|
+
}, async ({ project, environment, applicationSlug, pageSlug, viewport, fullPage, delayMs, captureUnready, anonymous, asUser, cookies, layoutReport, actions, outPath, expectFailure, language, colorScheme }) => {
|
|
416
477
|
try {
|
|
417
478
|
if (anonymous === true && (asUser !== undefined || cookies !== undefined)) {
|
|
418
479
|
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.");
|
|
419
480
|
}
|
|
481
|
+
const incompleteDrag = actions?.findIndex(step => step.action === 'drag' && (step.target === undefined || step.to === undefined)) ?? -1;
|
|
482
|
+
if (incompleteDrag >= 0) {
|
|
483
|
+
throw new PlatformApiError(`page_screenshot: actions[${incompleteDrag}] 'drag' adımı hem target (sürüklenen öğe) hem to (bırakma hedefi) ister — ör. {action:'drag', target:'kb_board__card-42', to:'kb_board__col-Assigned'}.`);
|
|
484
|
+
}
|
|
420
485
|
if (!config.studioBaseUrl) {
|
|
421
486
|
throw new PlatformApiError("page_screenshot için STUDIO_BASE_URL tanımlı değil. Studio arayüzünün kök adresini .env'e ekleyin (yerel geliştirmede http://localhost:3000, dağıtımda nginx adresi).");
|
|
422
487
|
}
|
|
423
488
|
const resolvedEnvironment = platform.resolveEnvironment(environment);
|
|
424
|
-
|
|
489
|
+
// U-04 (2026-09-09): kısıt önce gateway'de DEĞİL Studio'da olduğu için reddediliyordu —
|
|
490
|
+
// runtime rotası dal adı taşımıyor, ortamı modül içi bir store belirliyordu ve dal adıyla
|
|
491
|
+
// istenen kare development'ı gösterirdi. Studio artık `/branch/{env}/…` şeridini tanıyor ve
|
|
492
|
+
// ortamı URL'den okuyor (f-02); dal karesi bu yüzden GERÇEKTEN o dalın karesi. Geriye yalnız
|
|
493
|
+
// platformun dal diye kabul etmeyeceği adlar kalıyor — onlar için şerit kurulmaz.
|
|
494
|
+
const lane = resolvedEnvironment === 'development'
|
|
495
|
+
? 'dev'
|
|
496
|
+
: resolvedEnvironment === 'production'
|
|
497
|
+
? 'view'
|
|
498
|
+
: isBranchName(resolvedEnvironment) ? `branch/${resolvedEnvironment}` : undefined;
|
|
425
499
|
if (!lane) {
|
|
426
|
-
throw new PlatformApiError(`page_screenshot
|
|
500
|
+
throw new PlatformApiError(`page_screenshot '${resolvedEnvironment}' ortamını görüntüleyemez: bu ad ne sabit bir ortam ne de geçerli bir dal adı. Sabitler 'development' (/dev) ve 'production' (/view); dal adı bir harfle başlar, yalnız küçük harf/rakam/alt çizgi taşır (3-31 karakter) ve rezerve olamaz ('public', 'api', 'pg_*' gibi). Projedeki dalları environment_list ile görün.`);
|
|
427
501
|
}
|
|
428
|
-
const
|
|
502
|
+
const projectSlug = platform.resolveProject(project);
|
|
503
|
+
const languageChoice = language === undefined ? undefined : normalizeLanguageCode(language, 'language');
|
|
504
|
+
if (languageChoice) {
|
|
505
|
+
// Bağ burada kurulmazsa sessiz yanlış kare doğar: runtime tanımadığı kodu yok sayar ve
|
|
506
|
+
// sayfayı varsayılan dilde çizer (resolveRuntimeLanguage yalnız projenin taşıdığı kodu
|
|
507
|
+
// seçer), kare de 'ikinci dil' diye rapora girerdi.
|
|
508
|
+
const state = readProjectLanguages(await platform.org('GET', `/p/${projectSlug}`).catch(() => undefined));
|
|
509
|
+
if (state && state.languages.length > 0 && !state.languages.includes(languageChoice.code)) {
|
|
510
|
+
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.`);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
const url = `${config.studioBaseUrl}/${lane}/${projectSlug}/${applicationSlug}/${pageSlug}${languageChoice ? `?lang=${languageChoice.code}` : ''}`;
|
|
429
514
|
const size = viewport ?? { width: 1280, height: 800 };
|
|
430
515
|
const extraCookies = Object.entries(cookies ?? {}).map(([name, value]) => ({ name, value }));
|
|
431
516
|
const identity = anonymous === true ? 'anonymous' : asUser === undefined ? 'gateway' : 'asUser';
|
|
@@ -441,11 +526,15 @@ export function registerPageTools(server, platform, injectedScreenshotter) {
|
|
|
441
526
|
delayMs: delayMs ?? 0,
|
|
442
527
|
...(captureUnready === true ? { captureUnready: true } : {}),
|
|
443
528
|
...(layoutReport === true ? { layoutReport: true } : {}),
|
|
529
|
+
...(colorScheme === undefined ? {} : { colorScheme }),
|
|
444
530
|
...(actions === undefined ? {} : { actions: actions })
|
|
445
531
|
});
|
|
446
532
|
const meta = {
|
|
447
533
|
url,
|
|
448
534
|
environment: resolvedEnvironment,
|
|
535
|
+
...(languageChoice ? { language: languageChoice.code } : {}),
|
|
536
|
+
...(languageChoice?.note ? { languageNote: languageChoice.note } : {}),
|
|
537
|
+
...(colorScheme === undefined ? {} : { colorScheme }),
|
|
449
538
|
viewport: size,
|
|
450
539
|
fullPage: fullPage === true,
|
|
451
540
|
identity,
|
|
@@ -507,8 +596,12 @@ export function registerPageTools(server, platform, injectedScreenshotter) {
|
|
|
507
596
|
...(shot.actions?.some(outcome => !outcome.found)
|
|
508
597
|
? { actionsNote: `Bulunamayan hedef var: ${shot.actions.filter(outcome => !outcome.found).map(outcome => outcome.target ?? '?').join(' · ')}. elementId'leri page_schema_get ile doğrulayın; runtime düğümü data-element-id ile bulunuyor.` }
|
|
509
598
|
: {}),
|
|
510
|
-
...(shot.actions?.some(outcome => outcome.
|
|
511
|
-
? { actionsNote: `
|
|
599
|
+
...(shot.actions?.some(outcome => outcome.propWritten === false)
|
|
600
|
+
? { actionsNote: `Değer DOM'a yazıldı ama runtime prop'una ULAŞMADI: ${shot.actions.filter(outcome => outcome.propWritten === false).map(outcome => `${outcome.action} '${outcome.target ?? '?'}'`).join(' · ')}. Kare dolu kutu gösterse de akış bu değeri okumaz — sonucu doğrulanmış saymayın; adımın note alanına bakın.` }
|
|
601
|
+
: {}),
|
|
602
|
+
// 'wait' bir eylem değil bekleyiştir: akış sayacı ona bağlanmaz, notu tetiklemez (33/S-1).
|
|
603
|
+
...(shot.actions?.some(outcome => outcome.action !== 'wait' && outcome.applied && outcome.requests === 0 && (outcome.flowRuns ?? 0) === 0)
|
|
604
|
+
? { actionsNote: `Uygulandı ama HİÇ AĞ İSTEĞİ çıkmadı ve HİÇBİR AKIŞ KOŞMADI: ${shot.actions.filter(outcome => outcome.action !== 'wait' && outcome.applied && outcome.requests === 0 && (outcome.flowRuns ?? 0) === 0).map(outcome => `${outcome.action} '${outcome.target ?? '?'}'`).join(' · ')}. Olay bağı şemada görünse bile akış koşmamış olabilir — bağın gerçekten kurulu olduğunu page_runtime_get ile, akışın gövdesini flow_get ile doğrulayın.` }
|
|
512
605
|
: {}),
|
|
513
606
|
...(shot.layout === undefined
|
|
514
607
|
? {}
|
|
@@ -594,22 +687,29 @@ export function registerPageTools(server, platform, injectedScreenshotter) {
|
|
|
594
687
|
// kuru koşu ile yazma AYNI kararı vermeli (P0-2 Step 3).
|
|
595
688
|
const opRecords = allOps.filter(entry => !['remove_event', 'resize_component', 'set_prop_type'].includes(String(entry.op)));
|
|
596
689
|
const normalized = normalizeSchemaOps(opRecords);
|
|
597
|
-
const validationIssues =
|
|
690
|
+
const validationIssues = [
|
|
691
|
+
...schemaOpValidationErrors(opRecords).map(issue => `PAGE-016 — ${issue}`),
|
|
692
|
+
...codePropTemplateErrors(opRecords).map(issue => `PAGE-032 — ${issue}`)
|
|
693
|
+
];
|
|
694
|
+
const ordering = await orderByIndex(opRecords, () => fetchPage(platform, project, environment, applicationId, pageId));
|
|
598
695
|
const response = ((await platform.tenant('POST', project, `/pages/${applicationId}/${pageId}/schema/validate`, {
|
|
599
696
|
environment,
|
|
600
|
-
body: { ops:
|
|
697
|
+
body: { ops: ordering.ops }
|
|
601
698
|
})) ?? {});
|
|
602
699
|
if (validationIssues.length > 0)
|
|
603
|
-
response.validationWarnings = validationIssues
|
|
700
|
+
response.validationWarnings = validationIssues;
|
|
701
|
+
if (ordering.warnings.length > 0)
|
|
702
|
+
response.layoutWarnings = ordering.warnings;
|
|
604
703
|
return withNormalization(response, [
|
|
605
704
|
...componentDefaultsNote(normalized.components),
|
|
606
705
|
...validationNote(normalized.validations),
|
|
607
|
-
...replacePropNote(normalized.props)
|
|
706
|
+
...replacePropNote(normalized.props),
|
|
707
|
+
...flexIndexOrderNote(ordering.moved)
|
|
608
708
|
]);
|
|
609
709
|
}));
|
|
610
710
|
server.registerTool('page_schema_patch', {
|
|
611
711
|
title: 'Sayfa şemasına patch uygula',
|
|
612
|
-
description: "Micro-op'ları doğrulama duvarından geçirip kaydeder; açık studio canvas'ları SignalR ile canlı güncellenir. expectedUpdatedAt verilirse sayfa o okumadan beri değişmişse istek reddedilir (stale plan koruması). Yanıttaki appliedPatch, her op'un şemada tam olarak hangi JSON Pointer'a dokunduğunu söyler; gövdesi VARSAYILAN OLARAK iskelettir (eklenen düğüm id/type/children'a iner) — tam gövde için view:'full'. Op'un gerçekten kaydedilip kaydedilmediğini gövde değil silentlyDropped söyler.",
|
|
712
|
+
description: "Micro-op'ları doğrulama duvarından geçirip kaydeder; açık studio canvas'ları SignalR ile canlı güncellenir. expectedUpdatedAt verilirse sayfa o okumadan beri değişmişse istek reddedilir (stale plan koruması). Yanıttaki appliedPatch, her op'un şemada tam olarak hangi JSON Pointer'a dokunduğunu söyler; gövdesi VARSAYILAN OLARAK iskelettir (eklenen düğüm id/type/children'a iner) — tam gövde için view:'full'. Op'un gerçekten kaydedilip kaydedilmediğini gövde değil silentlyDropped söyler. Gateway ön kontrolleri (PAGE-006/008/016/032, set_prop_type gövdesi, move_component size) op başına toplanır: düşen HER op tek hata yanıtında rejectedOps[] ({ index, op, target, code, message }) ile listelenir ve yama atomik olduğu için hiçbir op gönderilmez — hepsini düzeltip listeyi bir kez yeniden gönderin.",
|
|
613
713
|
inputSchema: {
|
|
614
714
|
project: projectParam,
|
|
615
715
|
environment: environmentParam,
|
|
@@ -628,17 +728,19 @@ export function registerPageTools(server, platform, injectedScreenshotter) {
|
|
|
628
728
|
const resizeOps = allOps.filter(entry => entry.op === 'resize_component');
|
|
629
729
|
const propTypeOps = allOps.filter(entry => entry.op === 'set_prop_type');
|
|
630
730
|
const opRecords = allOps.filter(entry => !['remove_event', 'resize_component', 'set_prop_type'].includes(String(entry.op)));
|
|
631
|
-
const
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
731
|
+
const rejected = [];
|
|
732
|
+
const reject = (code, entry, messages) => {
|
|
733
|
+
for (const message of messages) {
|
|
734
|
+
rejected.push({ index: allOps.indexOf(entry), op: String(entry.op ?? ''), target: typeof entry.target === 'string' ? entry.target : null, code, message });
|
|
735
|
+
}
|
|
736
|
+
};
|
|
737
|
+
for (const entry of propTypeOps)
|
|
738
|
+
reject('SET-PROP-TYPE', entry, setPropTypeErrors([entry]));
|
|
739
|
+
for (const entry of opRecords.filter(record => record.op === 'move_component' && record.size !== undefined)) {
|
|
740
|
+
reject('MOVE-SIZE', entry, ["move_component yalnız 'position' uygular; gövdeye konan 'size' platformda SESSİZCE düşer (ölçüldü 2026-09-04, appliedPatch'te yalnız /blocks/0/position görünür). Boyut için resize_component kullanın: { op: 'resize_component', target: '<elementId>', size: { w, h } } — flex kapsayıcı içindeyse 'appearance'."]);
|
|
639
741
|
}
|
|
640
|
-
|
|
641
|
-
|
|
742
|
+
for (const entry of opRecords.filter(record => (record.op === 'remove_component' || record.op === 'move_component') && record.target === PAGE_CONTAINER_ELEMENT_ID)) {
|
|
743
|
+
reject('PAGE-006', entry, [`'${PAGE_CONTAINER_ELEMENT_ID}' sayfanın kök kapsayıcısıdır; silinmez ve taşınmaz — silinirse sayfa iskeleti bozulur ve Studio komponentleri kapsayıcı dışında bırakır. Komponent kaldırmak için remove_component'i kapsayıcının İÇİNDEKİ elementId'lerle kullanın (page_schema_get ile listeleyin).`]);
|
|
642
744
|
}
|
|
643
745
|
let cachedPage;
|
|
644
746
|
const readPage = async () => {
|
|
@@ -653,19 +755,10 @@ export function registerPageTools(server, platform, injectedScreenshotter) {
|
|
|
653
755
|
}
|
|
654
756
|
}
|
|
655
757
|
const normalized = normalizeSchemaOps(opRecords);
|
|
656
|
-
const
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
}
|
|
661
|
-
const wantsRootAdd = opRecords.some(entry => entry.op === 'add_component' && (entry.parent === undefined || entry.parent === null || entry.parent === 'root'));
|
|
662
|
-
let retargeted = [];
|
|
663
|
-
if (wantsRootAdd) {
|
|
664
|
-
const page = await readPage();
|
|
665
|
-
if ((page.schema ?? []).some(node => node.elementId === PAGE_CONTAINER_ELEMENT_ID)) {
|
|
666
|
-
retargeted = retargetRootAdds(opRecords, PAGE_CONTAINER_ELEMENT_ID);
|
|
667
|
-
}
|
|
668
|
-
}
|
|
758
|
+
for (const entry of opRecords)
|
|
759
|
+
reject('PAGE-016', entry, schemaOpValidationErrors([entry]));
|
|
760
|
+
for (const entry of opRecords)
|
|
761
|
+
reject('PAGE-032', entry, codePropTemplateErrors([entry]));
|
|
669
762
|
if (opRecords.some(entry => entry.op === 'set_event')) {
|
|
670
763
|
const typesByElementId = new Map();
|
|
671
764
|
try {
|
|
@@ -676,10 +769,21 @@ export function registerPageTools(server, platform, injectedScreenshotter) {
|
|
|
676
769
|
}
|
|
677
770
|
collectTypes(opRecords.map(entry => entry.component).filter(Boolean), typesByElementId);
|
|
678
771
|
const eventsBySlug = await fetchComponentEventNames(platform, project, environment, applicationId).catch(() => new Map());
|
|
679
|
-
const
|
|
680
|
-
|
|
681
|
-
|
|
772
|
+
for (const entry of opRecords)
|
|
773
|
+
reject('PAGE-008', entry, unknownEventOps([entry], typesByElementId, eventsBySlug));
|
|
774
|
+
}
|
|
775
|
+
if (rejected.length > 0)
|
|
776
|
+
throw new PlatformApiError(rejectedOpsMessage(rejected), 400);
|
|
777
|
+
const wantsRootAdd = opRecords.some(entry => entry.op === 'add_component' && (entry.parent === undefined || entry.parent === null || entry.parent === 'root'));
|
|
778
|
+
let retargeted = [];
|
|
779
|
+
if (wantsRootAdd) {
|
|
780
|
+
const page = await readPage();
|
|
781
|
+
if ((page.schema ?? []).some(node => node.elementId === PAGE_CONTAINER_ELEMENT_ID)) {
|
|
782
|
+
retargeted = retargetRootAdds(opRecords, PAGE_CONTAINER_ELEMENT_ID);
|
|
783
|
+
}
|
|
682
784
|
}
|
|
785
|
+
const ordering = await orderByIndex(opRecords, readPage);
|
|
786
|
+
const sentOps = ordering.ops;
|
|
683
787
|
// Akış silen op'lar yamadan ÖNCE hesaplanır: uyarı sonradan geldiğinde akış zaten kalıcı
|
|
684
788
|
// silinmiş oluyordu ve "önce yedekle" tavsiyesi uygulanamıyordu (09-05 koşusu, G-21).
|
|
685
789
|
let deletedFlows = [];
|
|
@@ -703,6 +807,7 @@ export function registerPageTools(server, platform, injectedScreenshotter) {
|
|
|
703
807
|
const formWarnings = [...formBindingWarnings(opRecords), ...formSubmitButtonWarnings(opRecords)];
|
|
704
808
|
let flowWarnings = [];
|
|
705
809
|
let layoutWarnings = [];
|
|
810
|
+
let flowsBeforePatch;
|
|
706
811
|
if (opRecords.some(entry => entry.op === 'remove_component')) {
|
|
707
812
|
try {
|
|
708
813
|
const page = await readPage();
|
|
@@ -716,7 +821,8 @@ export function registerPageTools(server, platform, injectedScreenshotter) {
|
|
|
716
821
|
try {
|
|
717
822
|
const hosted = await platform.tenant('GET', project, flowPath('page', applicationId, pageId), { environment });
|
|
718
823
|
const page = await readPage();
|
|
719
|
-
|
|
824
|
+
flowsBeforePatch = hosted?.data ?? [];
|
|
825
|
+
flowWarnings = [...flowWarnings, ...staleSetterWarnings(flowsBeforePatch, opRecords, page.schema)];
|
|
720
826
|
}
|
|
721
827
|
catch {
|
|
722
828
|
// akış listesi okunamadı
|
|
@@ -729,7 +835,7 @@ export function registerPageTools(server, platform, injectedScreenshotter) {
|
|
|
729
835
|
try {
|
|
730
836
|
const page = await readPage();
|
|
731
837
|
const simulated = JSON.parse(JSON.stringify(page.schema ?? []));
|
|
732
|
-
const touched = simulateLayoutOps(simulated,
|
|
838
|
+
const touched = simulateLayoutOps(simulated, sentOps);
|
|
733
839
|
// PAGE-013 op'lar UYGULANDIKTAN SONRAKİ ağaca bakar: tek başına eklenen düğüm masum
|
|
734
840
|
// görünür, iç içeliği ancak hedef parent zinciri belli olunca anlarsın.
|
|
735
841
|
nestedAfterOps = nestedRepeaterNodes(simulated);
|
|
@@ -738,7 +844,8 @@ export function registerPageTools(server, platform, injectedScreenshotter) {
|
|
|
738
844
|
...verticalGapWarnings(simulated, 'PAGE-010 (uyarı)', touched),
|
|
739
845
|
...stringStyleWarning(stringStyleNodes(simulated)),
|
|
740
846
|
...inertFlexPropWarning(inertFlexPropNodes(simulated)),
|
|
741
|
-
...flexSizeWarnings(simulated, 'PAGE-017 (uyarı)', 'PAGE-018 (uyarı)')
|
|
847
|
+
...flexSizeWarnings(simulated, 'PAGE-017 (uyarı)', 'PAGE-018 (uyarı)'),
|
|
848
|
+
...spacingLayerWarnings(simulated, 'PAGE-029 (uyarı)', 'PAGE-030 (uyarı)')
|
|
742
849
|
];
|
|
743
850
|
}
|
|
744
851
|
catch {
|
|
@@ -762,8 +869,9 @@ export function registerPageTools(server, platform, injectedScreenshotter) {
|
|
|
762
869
|
: [];
|
|
763
870
|
if (propTypeOps.length > 0)
|
|
764
871
|
cachedPage = undefined;
|
|
765
|
-
|
|
766
|
-
|
|
872
|
+
layoutWarnings = [...ordering.warnings, ...layoutWarnings];
|
|
873
|
+
const response = sentOps.length > 0
|
|
874
|
+
? ((await platform.tenant('PATCH', project, `/pages/${applicationId}/${pageId}/schema`, { environment, body: { ops: sentOps } })) ?? {})
|
|
767
875
|
: { valid: true };
|
|
768
876
|
if (view !== 'full')
|
|
769
877
|
outlineWriteResponse(response);
|
|
@@ -781,12 +889,25 @@ export function registerPageTools(server, platform, injectedScreenshotter) {
|
|
|
781
889
|
response.formWarnings = formWarnings;
|
|
782
890
|
if (flowWarnings.length > 0)
|
|
783
891
|
response.flowWarnings = flowWarnings;
|
|
892
|
+
// FLOW-018 yazma sonrası (G-04): uyarı yamadan önce "kopabilir" der; kopup kopmadığını ancak
|
|
893
|
+
// akışları yeniden okumak söyler. Okuma başarısız olursa yama yine geçerlidir.
|
|
894
|
+
if (flowsBeforePatch && flowWarnings.length > 0 && opRecords.some(entry => entry.op === 'add_component')) {
|
|
895
|
+
try {
|
|
896
|
+
const hosted = await platform.tenant('GET', project, flowPath('page', applicationId, pageId), { environment });
|
|
897
|
+
const lost = lostSetterTargets(flowsBeforePatch, hosted?.data ?? [], opRecords);
|
|
898
|
+
if (lost.length > 0)
|
|
899
|
+
response.flowTargetsLost = lost;
|
|
900
|
+
}
|
|
901
|
+
catch {
|
|
902
|
+
// akış listesi yeniden okunamadı
|
|
903
|
+
}
|
|
904
|
+
}
|
|
784
905
|
if (layoutWarnings.length > 0)
|
|
785
906
|
response.layoutWarnings = layoutWarnings;
|
|
786
907
|
// G-23 micro-op ayağı: yama 200 döner ve `appliedPatch` op'un ŞEMAYA yazıldığını söyler, ama
|
|
787
908
|
// alanın gerçekten KAYDEDİLDİĞİNİ söylemez. Yamadan sonra şemayı bir kez okuyup op'un yazmak
|
|
788
909
|
// istediğiyle kayıtlı olanı karşılaştırıyoruz. Okuma başarısız olursa yama yine geçerlidir.
|
|
789
|
-
const contentOps =
|
|
910
|
+
const contentOps = sentOps.filter(entry => entry.op === 'add_component' || entry.op === 'replace_prop');
|
|
790
911
|
let opDrops = [];
|
|
791
912
|
if (contentOps.length > 0) {
|
|
792
913
|
try {
|
|
@@ -802,7 +923,8 @@ export function registerPageTools(server, platform, injectedScreenshotter) {
|
|
|
802
923
|
...componentDefaultsNote(normalized.components),
|
|
803
924
|
...validationNote(normalized.validations),
|
|
804
925
|
...catalogNotes,
|
|
805
|
-
...replacePropNote(normalized.props)
|
|
926
|
+
...replacePropNote(normalized.props),
|
|
927
|
+
...flexIndexOrderNote(ordering.moved)
|
|
806
928
|
]), opDrops);
|
|
807
929
|
}));
|
|
808
930
|
server.registerTool('page_create', {
|
|
@@ -862,7 +984,7 @@ export function registerPageTools(server, platform, injectedScreenshotter) {
|
|
|
862
984
|
saved.schema = saved.schema.map(node => summarize(node));
|
|
863
985
|
}
|
|
864
986
|
const normalized = withWriteDiff(withNormalization(response, [...container.fixes, ...reset, ...componentDefaultsNote(fixed), ...catalogNotes, ...reorderNote(moved)]), drops);
|
|
865
|
-
const createWarnings = [...gaps, ...stringStyleWarning(stringStyles), ...inertFlexPropWarning(inertFlexPropNodes(container.schema)), ...flexSizeWarnings(container.schema, 'PAGE-017 (uyarı)', 'PAGE-018 (uyarı)')];
|
|
987
|
+
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ı)')];
|
|
866
988
|
if (createWarnings.length === 0 || !normalized || typeof normalized !== 'object')
|
|
867
989
|
return normalized;
|
|
868
990
|
return { ...normalized, layoutWarnings: createWarnings };
|
|
@@ -896,7 +1018,7 @@ export function registerPageTools(server, platform, injectedScreenshotter) {
|
|
|
896
1018
|
const moved = overrides.schema === undefined ? [] : reorderSchemaBlocks(overrides.schema);
|
|
897
1019
|
const gaps = overrides.schema === undefined
|
|
898
1020
|
? []
|
|
899
|
-
: [...verticalGapWarnings(overrides.schema, 'PAGE-010 (uyarı)'), ...inertFlexPropWarning(inertFlexPropNodes(overrides.schema)), ...flexSizeWarnings(overrides.schema, 'PAGE-017 (uyarı)', 'PAGE-018 (uyarı)')];
|
|
1021
|
+
: [...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ı)')];
|
|
900
1022
|
const body = pageUpdateBody(current, overrides);
|
|
901
1023
|
await platform.tenant('PUT', project, `/pages/${applicationId}/${pageId}`, { environment, body });
|
|
902
1024
|
const summary = {
|
|
@@ -61,6 +61,7 @@ const guideRouting = {
|
|
|
61
61
|
'modify-existing': 'mevcut yapıyı bozmadan değiştirme disiplini',
|
|
62
62
|
workflow: 'DTO-önce akışı, branch bağlama, tip×step matrisi',
|
|
63
63
|
microservice: 'kaynak seçimi (table > query), selectFields disiplini',
|
|
64
|
+
microfunction: 'kendi runtime\'ında koşan Node/Python fonksiyonu: protokol, yayın döngüsü, step kullanımı',
|
|
64
65
|
entity: "alan tipleri, ilişkiler, hazır entity'ler",
|
|
65
66
|
page: 'sayfa/flow işleri; flex düzen prop grubu ve boyut modları (Kural 1f-2)',
|
|
66
67
|
layout: 'yerleşim kabuğu: tek slot, views, mobil yığılma, widthType fill/fixed/auto',
|
|
@@ -70,7 +71,9 @@ const guideRouting = {
|
|
|
70
71
|
middleware: 'auth/oturum/yetki',
|
|
71
72
|
view: 'tam metin arama indeksi',
|
|
72
73
|
expressions: 'ifade fonksiyon/bağlam sözlüğü (system.*, file.*, Page.*, state.*, Steps…)',
|
|
73
|
-
|
|
74
|
+
functions: "fonksiyonlar, iki kaynak tek biçim: source:'studio' tarayıcıda Functions.<ad>() (kapsam uygulama/sayfa, UI flow 'function' adımı), source:'workflow' sunucuda çıplak ad; parametre/varsayılan/required, tuzaklar",
|
|
75
|
+
translation: 'sayfa/içerik çevirileri (çoklu dil)',
|
|
76
|
+
merge: "dal aç, dalda çalış, development'a merge et; çakışma çözümü ve apply kapıları"
|
|
74
77
|
};
|
|
75
78
|
// page.md 123 KB; section'sız dönerse yanıt tool çıktı sınırını aşıyor ve rehber MCP kanalından
|
|
76
79
|
// HİÇ okunamıyor (27/05 E-1, 09-04 koşusu). Eşik ÖLÇÜLEN kopma noktasından türetilir, "bugün
|