@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.
Files changed (98) hide show
  1. package/.env.example +2 -2
  2. package/.env.production.example +8 -0
  3. package/README.md +9 -8
  4. package/dist/config.js +18 -3
  5. package/dist/knowledge/backendConstraints.js +88 -0
  6. package/dist/knowledge/componentCatalog.js +415 -68
  7. package/dist/knowledge/componentEvents.js +120 -25
  8. package/dist/knowledge/functionGuards.js +134 -0
  9. package/dist/knowledge/normalize.js +512 -25
  10. package/dist/knowledge/outlines.js +31 -0
  11. package/dist/knowledge/policy.js +14 -5
  12. package/dist/knowledge/propTypes.js +5 -4
  13. package/dist/knowledge/rules.js +453 -6
  14. package/dist/knowledge/styleResolve.js +6 -1
  15. package/dist/platform/csv.js +123 -0
  16. package/dist/platform/errors.js +62 -3
  17. package/dist/platform/http.js +11 -2
  18. package/dist/platform/responses.js +16 -4
  19. package/dist/platform/screenshot.js +98 -9
  20. package/dist/platform/strictArguments.js +32 -0
  21. package/dist/platform/writeDiff.js +6 -3
  22. package/dist/resources/examples/flow-call-function.json +66 -0
  23. package/dist/resources/examples/function-total.json +70 -0
  24. package/dist/resources/examples/microfunction-node-sum.json +56 -0
  25. package/dist/resources/examples/page-dynamic-form.json +1 -1
  26. package/dist/resources/examples/page-sign-in.json +2 -2
  27. package/dist/resources/guides/component.md +14 -2
  28. package/dist/resources/guides/entity.md +321 -17
  29. package/dist/resources/guides/expressions.md +9 -0
  30. package/dist/resources/guides/functions.md +262 -0
  31. package/dist/resources/guides/getting-started.md +7 -0
  32. package/dist/resources/guides/layout.md +6 -0
  33. package/dist/resources/guides/merge.md +231 -0
  34. package/dist/resources/guides/microfunction.md +166 -0
  35. package/dist/resources/guides/microservice.md +166 -7
  36. package/dist/resources/guides/modify-existing.md +23 -0
  37. package/dist/resources/guides/page.md +101 -22
  38. package/dist/resources/guides/translation.md +35 -10
  39. package/dist/resources/guides/workflow.md +51 -2
  40. package/dist/resources/schema-fingerprint.json +4 -4
  41. package/dist/resources/schemas/AnalyzeImportQuery.json +51 -0
  42. package/dist/resources/schemas/ApplyMergePlanCommand.json +19 -0
  43. package/dist/resources/schemas/CreateBulkRowCommand.json +3 -0
  44. package/dist/resources/schemas/CreateEnvironmentCommand.json +31 -0
  45. package/dist/resources/schemas/CreateGlobalFunctionCommand.json +95 -0
  46. package/dist/resources/schemas/CreateImportPlanCommand.json +278 -0
  47. package/dist/resources/schemas/CreateMergePlanCommand.json +34 -0
  48. package/dist/resources/schemas/CreateMicrofunctionCommand.json +117 -0
  49. package/dist/resources/schemas/CreateMicrofunctionRuntimeCommand.json +51 -0
  50. package/dist/resources/schemas/CreateMicroserviceCommand.json +123 -2
  51. package/dist/resources/schemas/CreateWorkflowCommand.json +17 -3
  52. package/dist/resources/schemas/DataJobDto.json +267 -0
  53. package/dist/resources/schemas/ExportRowsCommand.json +172 -0
  54. package/dist/resources/schemas/Filter.json +3 -1
  55. package/dist/resources/schemas/GetRowsQuery.json +3 -1
  56. package/dist/resources/schemas/GlobalFunctionDto.json +89 -0
  57. package/dist/resources/schemas/GlobalFunctionUpdateResultDto.json +249 -0
  58. package/dist/resources/schemas/ImportAnalysisDto.json +401 -0
  59. package/dist/resources/schemas/ImportPlanDto.json +260 -0
  60. package/dist/resources/schemas/ImportSampleDto.json +90 -0
  61. package/dist/resources/schemas/InvokeMicrofunctionCommand.json +25 -0
  62. package/dist/resources/schemas/PreviewMicroserviceCommand.json +123 -2
  63. package/dist/resources/schemas/PreviewWorkflowCommand.json +17 -3
  64. package/dist/resources/schemas/ResolveMergeConflictsCommand.json +58 -0
  65. package/dist/resources/schemas/RevertMergePlanCommand.json +19 -0
  66. package/dist/resources/schemas/RunImportCommand.json +62 -0
  67. package/dist/resources/schemas/StepDto.json +17 -3
  68. package/dist/resources/schemas/UpdateBulkRowCommand.json +3 -0
  69. package/dist/resources/schemas/UpdateEntityCommand.json +3 -1
  70. package/dist/resources/schemas/UpdateGlobalFunctionCommand.json +66 -0
  71. package/dist/resources/schemas/UpdateMicrofunctionCommand.json +121 -0
  72. package/dist/resources/schemas/UpdateMicrofunctionRuntimeCommand.json +54 -0
  73. package/dist/resources/schemas/UpdateMicroserviceCommand.json +123 -2
  74. package/dist/resources/schemas/UpdateWorkflowCommand.json +17 -3
  75. package/dist/server.js +17 -4
  76. package/dist/tools/activities.js +40 -0
  77. package/dist/tools/automations.js +14 -5
  78. package/dist/tools/common.js +16 -0
  79. package/dist/tools/dependencies.js +1 -1
  80. package/dist/tools/environments.js +52 -0
  81. package/dist/tools/files.js +51 -50
  82. package/dist/tools/flows.js +104 -5
  83. package/dist/tools/functions.js +331 -0
  84. package/dist/tools/importSchemas.js +79 -0
  85. package/dist/tools/imports.js +241 -0
  86. package/dist/tools/layouts.js +4 -4
  87. package/dist/tools/merges.js +113 -0
  88. package/dist/tools/microfunctions.js +155 -0
  89. package/dist/tools/microservices.js +112 -9
  90. package/dist/tools/pages.js +188 -66
  91. package/dist/tools/platformInfo.js +4 -1
  92. package/dist/tools/queries.js +72 -6
  93. package/dist/tools/revisions.js +94 -0
  94. package/dist/tools/rows.js +342 -22
  95. package/dist/tools/screenWrite.js +11 -2
  96. package/dist/tools/templates.js +32 -5
  97. package/dist/tools/workflows.js +28 -6
  98. package/package.json +3 -1
@@ -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 gönderilen ham değeri HER ZAMAN static Prop'una sarar ve sarmalanmış Prop nesnesini reddeder — kurulu bir etiketi çeviriye bağlamanın tek yolu düğümü remove_component + add_component ile yeniden kurmaktı ve o yol düğüme yazan setter iteratee'lerini sessizce öldürüyordu. 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.");
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şı ve verilen position'ı desktop block'una yaz) ve analiz
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 = [...(parent.children ?? []), node];
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 anahtarları. 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.",
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
- value: z.unknown().optional().describe("'set' için yazılacak değer"),
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
- const lane = resolvedEnvironment === 'development' ? 'dev' : resolvedEnvironment === 'production' ? 'view' : undefined;
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 yalnız 'development' (/dev) ve 'production' (/view) ortamlarını görüntüleyebilir; '${resolvedEnvironment}' için Studio'da karşılık gelen bir rota yok.`);
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 url = `${config.studioBaseUrl}/${lane}/${platform.resolveProject(project)}/${applicationSlug}/${pageSlug}`;
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.applied && outcome.requests === 0 && (outcome.flowRuns ?? 0) === 0)
511
- ? { actionsNote: `Uygulandı ama HİÇ AĞ İSTEĞİ çıkmadı ve HİÇBİR AKIŞ KOŞMADI: ${shot.actions.filter(outcome => 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.` }
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 = schemaOpValidationErrors(opRecords);
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: opRecords }
697
+ body: { ops: ordering.ops }
601
698
  })) ?? {});
602
699
  if (validationIssues.length > 0)
603
- response.validationWarnings = validationIssues.map(issue => `PAGE-016 — ${issue}`);
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 propTypeIssues = setPropTypeErrors(propTypeOps);
632
- if (propTypeIssues.length > 0)
633
- throw new PlatformApiError(`set_prop_type gövdesi eksik (istek gönderilmedi): ${propTypeIssues.join(' · ')}`, 400);
634
- const containerOps = opRecords
635
- .filter(entry => (entry.op === 'remove_component' || entry.op === 'move_component') && entry.target === PAGE_CONTAINER_ELEMENT_ID);
636
- const sizedMoves = opRecords.filter(entry => entry.op === 'move_component' && entry.size !== undefined);
637
- if (sizedMoves.length > 0) {
638
- throw new PlatformApiError(`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'. Etkilenen hedefler: ${sizedMoves.map(entry => entry.target).join(' · ')}`, 400);
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
- if (containerOps.length > 0) {
641
- throw new PlatformApiError(`'${PAGE_CONTAINER_ELEMENT_ID}' sayfanın kök kapsayıcısıdır; silinmez ve taşınmaz (PAGE-006) — 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).`, 400);
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 validationIssues = schemaOpValidationErrors(opRecords);
657
- if (validationIssues.length > 0) {
658
- throw new PlatformApiError(`PAGE-016 — validation gövdesi eksik (istek gönderilmedi): ${validationIssues.join(' · ')}. `
659
- + "Çalışan biçim 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 }.", 400);
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 unknown = unknownEventOps(opRecords, typesByElementId, eventsBySlug);
680
- if (unknown.length > 0)
681
- throw new PlatformApiError(`PAGE-008 — geçersiz olay adı (istek gönderilmedi): ${unknown.join(' · ')}`, 400);
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
- flowWarnings = [...flowWarnings, ...staleSetterWarnings(hosted?.data ?? [], opRecords, page.schema)];
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, opRecords);
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
- const response = opRecords.length > 0
766
- ? ((await platform.tenant('PATCH', project, `/pages/${applicationId}/${pageId}/schema`, { environment, body: { ops: opRecords } })) ?? {})
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 = opRecords.filter(entry => entry.op === 'add_component' || entry.op === 'replace_prop');
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
- translation: 'sayfa/içerik çevirileri (çoklu dil)'
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