@appaflytech/zeyrek-ai-mcp 0.0.40 → 0.0.42

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 (60) hide show
  1. package/dist/knowledge/componentCatalog.js +95 -104
  2. package/dist/knowledge/componentEvents.js +34 -18
  3. package/dist/knowledge/mobileComponentCatalog.js +119 -20
  4. package/dist/knowledge/mobileComponentEvents.js +41 -0
  5. package/dist/knowledge/motionResolve.js +252 -0
  6. package/dist/knowledge/normalize.js +117 -71
  7. package/dist/knowledge/pageDiscover.js +2 -2
  8. package/dist/knowledge/platform/batchOps.js +70 -0
  9. package/dist/knowledge/platform/heroTag.js +153 -0
  10. package/dist/knowledge/platform/lint.js +128 -6
  11. package/dist/knowledge/platform/mobile.js +1 -1
  12. package/dist/knowledge/platform/mobileLayoutLint.js +8 -1
  13. package/dist/knowledge/platform/mobileRules.js +18 -0
  14. package/dist/knowledge/platform/pageTransition.js +57 -0
  15. package/dist/knowledge/platform/web.js +1 -1
  16. package/dist/knowledge/rules.js +217 -24
  17. package/dist/knowledge/styleResolve.js +0 -1
  18. package/dist/knowledge/styleResolveNative.js +88 -4
  19. package/dist/platform/capture/actions.js +27 -1
  20. package/dist/platform/capture/capture.js +10 -13
  21. package/dist/platform/capture/persistent-browser.js +130 -0
  22. package/dist/platform/capture/summary.js +4 -1
  23. package/dist/resources/examples/flow-list-to-form.json +8 -10
  24. package/dist/resources/examples/flow-load-table.json +1 -1
  25. package/dist/resources/examples/mobile-app-shell-drawer.json +1 -0
  26. package/dist/resources/examples/mobile-list-detail-form.json +7 -3
  27. package/dist/resources/examples/mobile-scan-code.json +285 -0
  28. package/dist/resources/guides/component.md +3 -3
  29. package/dist/resources/guides/entity.md +16 -0
  30. package/dist/resources/guides/expressions.md +8 -7
  31. package/dist/resources/guides/merge.md +98 -18
  32. package/dist/resources/guides/microservice.md +3 -3
  33. package/dist/resources/guides/middleware.md +2 -0
  34. package/dist/resources/guides/mobile-layout.md +2 -1
  35. package/dist/resources/guides/mobile-publish.md +1 -0
  36. package/dist/resources/guides/mobile-steps.md +67 -0
  37. package/dist/resources/guides/mobile.md +51 -70
  38. package/dist/resources/guides/page.md +229 -137
  39. package/dist/resources/guides/translation.md +67 -1
  40. package/dist/resources/guides/workflow.md +31 -1
  41. package/dist/resources/schema-fingerprint.json +2 -2
  42. package/dist/resources/schemas/ResolveMergeConflictsCommand.json +30 -1
  43. package/dist/tools/activities.js +3 -1
  44. package/dist/tools/applications.js +16 -2
  45. package/dist/tools/components.js +12 -6
  46. package/dist/tools/designSchemaPatch.js +18 -13
  47. package/dist/tools/entities.js +41 -4
  48. package/dist/tools/layouts.js +29 -9
  49. package/dist/tools/merges.js +29 -9
  50. package/dist/tools/mobile.js +15 -1
  51. package/dist/tools/pages.js +156 -168
  52. package/dist/tools/platformInfo.js +3 -2
  53. package/dist/tools/profileWrite.js +2 -2
  54. package/dist/tools/queries.js +1 -1
  55. package/dist/tools/rows.js +14 -3
  56. package/dist/tools/schemaOps.js +132 -34
  57. package/dist/tools/screenWrite.js +3 -3
  58. package/dist/tools/screens.js +42 -3
  59. package/dist/tools/translations.js +148 -6
  60. package/package.json +2 -2
@@ -55,7 +55,8 @@ diye bir alan **hiç görünmez**; blok tam olarak bunu görünür kılmak için
55
55
  | Tool | Ne yapar |
56
56
  |---|---|
57
57
  | `translation_list` | Application'ın tüm `(language, key, value)` kayıtlarını düz liste döner |
58
- | `translation_update` | `keys` (ekle/güncelle, `componentId` opsiyonel) ve/veya `deletedKeys` (sil) — ikisi de aynı çağrıda verilebilir, en az biri dolu olmalı |
58
+ | `translation_update` | `keys` (ekle/güncelle, `componentId` opsiyonel) ve/veya `deletedKeys` (sil) — ikisi de aynı çağrıda verilebilir, en az biri dolu olmalı. `file` kipi: doldurulmuş Excel'i içe aktarır (aşağıda) |
59
+ | `translation_export` | Çevirmene verilecek Excel dosyasını yerel yola yazar (aşağıda) |
59
60
  | `project_update` | `languages`/`defaultLanguage` — proje dillerini kurar/değiştirir |
60
61
 
61
62
  ```jsonc
@@ -126,6 +127,32 @@ Son kullanıcı dili `language-selector` komponentiyle değiştirir (`platform_g
126
127
  prop'lar sayfa yenilenmeden yeniden çözülür — ayrı bir akış kurmana gerek yok, komponenti sayfaya
127
128
  eklemen yeterli. Projede dil tanımlı değilse (`languages` boşsa) liste boş çizilir.
128
129
 
130
+ **Akıştan dil (2026-10-14 frontend; uzak kurulumda dağıtımdan sonra):** kod adımında ve `{{ }}` bağında
131
+ `Page.language` etkin dil kodunu verir, `Page.setLanguage('en')` dili seçicinin yaptığı gibi değiştirir
132
+ (saklanır, bütün bağlar yeni dilde çözülür; projede olmayan kod yok sayılır). "Girişten sonra
133
+ kullanıcının kayıtlı diline geç" böyle kurulur — sayfayı yenilemek gerekmez. Seçicinin `self.value`'su
134
+ `mount`'ta boştur; dili okumak için `Page.language` kullan.
135
+
136
+ ## İfade başka komponentin çeviri bağlı prop'unu okuyunca
137
+
138
+ 2026-10-14 frontend'inden (uzak kurulumda dağıtımdan) sonra ifade, kod adımı, `self`, `Widget.x`, setter'ın
139
+ eleman kopyası ve uç argümanının Property kaynağı çeviriye bağlı prop'u **ekranda görünen metinle**
140
+ okur — iç içe dizilerdekiler dahil (`{{ menu.navigations }}` başlıklarla gelir, `drawer.actions.map(a =>
141
+ a.text)` düğme metinlerini verir). Akış okuduğu diziyi geri yazınca, yazılan metin o anki çeviriyle
142
+ aynıysa çeviri bağı korunur ve dil sonradan değişince o metin de değişir; farklı metin yazılırsa bağın
143
+ yerini o metin alır. Bu sürümden önceki kurulumda aynı okuma `null` verir (menü boş, geri yazılan düğme
144
+ yazısı kaybolur) — orada geri okunan diziyi çeviriye bağlama.
145
+
146
+ ## html şablonunda çeviri: `{{#t}}anahtar{{/t}}`
147
+
148
+ html bileşeninin `content`'inde `{{#t}}page.x.baslik{{/t}}` sözlükteki metni etkin dilde basar (zincir
149
+ ekrandakiyle aynı), HTML kaçışlıdır ve dil değişince yeniden çizilir. Bölümün içi önce çizildiği için
150
+ anahtar `context`'ten kurulabilir: `{{#t}}common.status.{{durum}}{{/t}}`. `context`'i akışlar baştan
151
+ yazsa da şablondaki metin etkilenmez — birden çok akışın doldurduğu html'deki sabit metni çevirmenin yolu
152
+ budur. Lambda yalnız `{{#t}}` geçen şablona eklenir; `context` kendi `t` alanını taşıyorsa o kazanır; markup taşıyan cümle yine `context` + `{{{…}}}` ile
153
+ basılır; datatable'ın `html` hücresi bu sözdizimini tanımaz. 2026-10-14 frontend'i (frontend ADR 0021),
154
+ uzak kurulumda dağıtımdan sonra.
155
+
129
156
  ## Kareyi ikinci dilde almak: `page_screenshot(language:)`
130
157
 
131
158
  Dil değiştirici **kanıt için gerekli değildir.** Runtime boot dilini şu sırayla seçer:
@@ -194,6 +221,45 @@ Runtime `t:` ile başlayan bir mesajı anahtar sayar ve normal zincirden çözer
194
221
  olduğu gibi gösterilir. Çözüm çalışma anında olduğu için şema varsayılanlarını değiştirmek
195
222
  gerekmez — `schema.ts`'e dokunulmaz, katalog yeniden yüklenmez.
196
223
 
224
+ ## Dosyayla toplu çeviri (Excel)
225
+
226
+ İnsan bir çevirmene yeni bir dil yaptıracaksa: sözlük Excel'e iner, çevirmen hedef sütunu doldurur,
227
+ dosya geri yüklenir. **Çeviriyi sen yapacaksan dosyaya gerek yok** — `translation_list` ile oku,
228
+ `translation_update { keys }` ile yaz.
229
+
230
+ ```jsonc
231
+ // 1. dışa aktar — dosya YALNIZ outPath'e yazılır, Sürücü'ye yüklenmez
232
+ { "applicationId": "<app id>", "targetLanguage": "es", "outPath": "C:/ceviri/crm-es.xlsx" } // translation_export
233
+ // → { keys: 120, targetMissing: 120, sourceLanguage: "en", note: "… TR-004 …" }
234
+
235
+ // 2. çevirmen C sütununu doldurur; 3. önce kuru koşu
236
+ { "applicationId": "<app id>", "file": "C:/ceviri/crm-es.xlsx", "targetLanguage": "es", "dryRun": true } // translation_update
237
+ // → { created: 118, skippedEmpty: 2, unknownKeyCount: 0, targetLanguageInProject: false, note: "… ekleyecek …" }
238
+
239
+ // 4. gerçek içe aktarma
240
+ { "applicationId": "<app id>", "file": "C:/ceviri/crm-es.xlsx", "targetLanguage": "es" } // translation_update
241
+ // → { created: 118, languageAdded: true, … }
242
+ ```
243
+
244
+ Dosya: tek sayfa, `A` Key · `B` kaynak dil (yalnız referans, okunmaz) · `C` hedef dil; başlıklar
245
+ `Key` · `Default Language (EN)` · `Target Language (ES)`. İçe aktarma **başlık satırını okumaz**:
246
+ değerler `translation_update`'in zorunlu `targetLanguage`'ındaki dile yazılır, `C1`'deki `(ES)` yalnız
247
+ çevirmen içindir ve dili belirlemez. Gizli `_zeyrek`
248
+ sayfası dosyanın hangi application'dan çıktığını söyler; başka bir application'ın dosyası yine
249
+ yazılır ama yanıt `applicationMismatch: true` ve not taşır.
250
+
251
+ - **Dolu hücre mevcut değeri ezer**, boş/boşluk hücre atlanır, hiçbir şey silinmez.
252
+ - **Application'da olmayan key açılmaz** — `unknownKeys`'e düşer. Yeni key `keys` kipiyle açılır.
253
+ - Key eşleşmesi önce birebir, yoksa büyük/küçük harf duyarsız tek aday; aynı key dosyada iki satırda
254
+ ise 422 ve hiçbir şey yazılmaz.
255
+ - **Hedef dil projede yoksa içe aktarma onu projeye ekler — bütün ortamlara** (TR-004). Önce
256
+ `project_update` gerekmez; ama dil production'a da gider ve orada çeviriler deployment'la gelene
257
+ kadar runtime varsayılan dile düşer.
258
+ - Dosya platformda **saklanmaz**: ne dışa aktarma ne içe aktarma Sürücü'ye bir şey bırakır.
259
+ - Studio'nun çeviri panelindeki JSON içe aktarma bundan farklıdır: o yalnız boşları doldurur ve
260
+ projede olmayan dili atlar.
261
+ - Sınırlar: `.xlsx` (`.xls`, csv, json reddedilir), 10 MB, 10.000 satır.
262
+
197
263
  ## Bilinen sınırlar
198
264
 
199
265
  - **Flow adımlarında Translation kaynağı YOK.** `value.type` sözlüğünde (`platform_guide('page')`
@@ -286,7 +286,10 @@ Global-error dalında `error` değişkeni tanımlıdır:
286
286
  ```
287
287
 
288
288
  Fail dalında aynı zincir `<adım adı>.error.cause`'tadır (`<adım adı>.error` = `{ code, message, detail,
289
- type, cause }`).
289
+ type, cause }`). Serbest `error` değişkenini YALNIZ global-error dalı kurar
290
+ (`ActivityContext.EnterGlobalErrorHandling`): bağlı `fail` dalının hedefi `error.message` okursa
291
+ `undefined` alır — orada `<adım adı>.error.message` / `.code` okuyun (WF-035 uyarır; hedef aynı zamanda
292
+ global-error dalının hedefi olsa da).
290
293
 
291
294
  `error.step` hangi adımın patladığını söyler. `input`/`data` alanları ifade değerlerini ve adım
292
295
  çıktısını taşır — gövdeye yalnız ihtiyaç duyduğun alt kümeyi yaz (gateway'in varsayılanı
@@ -575,6 +578,33 @@ Tam örnek, ölçülen çıktı şekli ve `code` çocuğu tuzağının çıkış
575
578
  > `The JSON value could not be converted to System.Guid` ile reddetti. Geçerli önek harfleri
576
579
  > yalnız `a`–`f`'tir.
577
580
 
581
+ ## `file-upload` adımı: çıktı, dosya alanının tablo okuma biçimidir (K10, 2026-10-08)
582
+
583
+ Adım `rule.fileString`'i (base64, JS ifadesi) `rule.folderId` klasörüne `rule.fileName` (JS ifadesi;
584
+ boşsa GUID) adıyla yazar, dosyayı geri okur ve **tablo okumasının FILE alanında döndürdüğü nesneyi**
585
+ üretir (`FileUploadStep.OutputSchema`; iki yol aynı `FilePathMapper`'ı kullanır):
586
+
587
+ ```json
588
+ {
589
+ "id": "0199c3a2-7b1e-7c4d-9a10-3f2b8c6d5e41",
590
+ "path": "uploads/photo.png",
591
+ "absolute_path": "https://acme.example.com/dev/uploads/photo.png",
592
+ "type": "ref",
593
+ "value": "0199c3a2-7b1e-7c4d-9a10-3f2b8c6d5e41",
594
+ "fileName": "photo.png"
595
+ }
596
+ ```
597
+
598
+ - Sonraki adımlar **adım adı + `.data`** ile okur: adres `yukle.data.absolute_path`, kimlik
599
+ `yukle.data.id` (`value` aynı id'dir). `absolute_path` = uygulamanın dosya sunum ucu (org slug +
600
+ ortamın depolama öneki) + `/` + `path`; `path` boşsa `""`. Görsel için ikinci okuma gerekmez.
601
+ - **Aynı içerik klasörde varsa** yeni dosya yazılmaz: `id`, adres ve `fileName` var olan dosyanındır.
602
+ - **Mobil görsel:** `response` adımı nesneyi döndürür (`bodyExpression: "({ foto: yukle.data })"`),
603
+ sayfa akışı `Steps.<sayfa adımı>.data.foto`'yu `image.src`'ye verir — nesnenin kendisi ya da
604
+ `.absolute_path`; `.value` dosya kimliğidir, görsel çizilmez (`platform_guide('mobile')` §Dosya ve fotoğraf).
605
+ - 2026-10-08'den eski backend imajı yalnız `{ id }` döndürür: `yukle.data.absolute_path` `undefined`
606
+ çıkıyorsa imaj bayattır. Kaynak: backend `FileUploadStepTests`; canlı koşuyla henüz ölçülmedi.
607
+
578
608
  ## Table step kuralları
579
609
 
580
610
  - `rule` içinde `datasourceId` + `entityId` zorunlu; `selectFields`/`filters` öğelerinde
@@ -1,6 +1,6 @@
1
1
  {
2
- "hash": "63d0b9efc3b8d0c0d8ef9c8b7478b3abf8cb482e4e80276202b3faed73cedddf",
3
- "generatedAt": "2026-10-08T09:32:48.956Z",
2
+ "hash": "ba18a5111614d9b1e63e0d50f1a06a4dce1081b3d482c88e7620ca4bda9a466b",
3
+ "generatedAt": "2026-10-14T08:45:02.944Z",
4
4
  "types": 337,
5
5
  "files": 167,
6
6
  "note": "Şemaları üreten backend kaynaklarının parmak izi. Elle düzenlemeyin: 'npm run generate-schemas' yazar, 'npm run schema:check' denetler.",
@@ -41,6 +41,9 @@
41
41
  },
42
42
  "choice": {
43
43
  "$ref": "#/components/schemas/NgApps.Domain.Contexts.Organization.Merges.MergeChoice"
44
+ },
45
+ "value": {
46
+ "$ref": "#/components/schemas/System.Text.Json.Nodes.JsonNode"
44
47
  }
45
48
  },
46
49
  "additionalProperties": false
@@ -48,10 +51,36 @@
48
51
  "NgApps.Domain.Contexts.Organization.Merges.MergeChoice": {
49
52
  "enum": [
50
53
  "source",
51
- "target"
54
+ "target",
55
+ "both",
56
+ "custom"
52
57
  ],
53
58
  "type": "string",
54
59
  "format": ""
60
+ },
61
+ "System.Text.Json.Nodes.JsonNode": {
62
+ "type": "object",
63
+ "properties": {
64
+ "options": {
65
+ "$ref": "#/components/schemas/System.Text.Json.Nodes.JsonNodeOptions"
66
+ },
67
+ "parent": {
68
+ "$ref": "#/components/schemas/System.Text.Json.Nodes.JsonNode"
69
+ },
70
+ "root": {
71
+ "$ref": "#/components/schemas/System.Text.Json.Nodes.JsonNode"
72
+ }
73
+ },
74
+ "additionalProperties": false
75
+ },
76
+ "System.Text.Json.Nodes.JsonNodeOptions": {
77
+ "type": "object",
78
+ "properties": {
79
+ "propertyNameCaseInsensitive": {
80
+ "type": "boolean"
81
+ }
82
+ },
83
+ "additionalProperties": false
55
84
  }
56
85
  }
57
86
  }
@@ -13,6 +13,8 @@ const activityKind = z.enum([
13
13
  'merge.applied_with_drift',
14
14
  'merge.discarded',
15
15
  'merge.failed',
16
+ 'merge.replanned',
17
+ 'merge.resolutions_carried',
16
18
  'deployment.started',
17
19
  'deployment.completed',
18
20
  'deployment.failed',
@@ -25,7 +27,7 @@ export function registerActivityTools(server, platform) {
25
27
  const config = platform.config;
26
28
  server.registerTool('activity_list', {
27
29
  title: 'Proje etkinlik günlüğü',
28
- description: "Projenin dal, merge ve dağıtım geçmişini en yeniden eskiye listeler: kim, ne zaman, ne yaptı. Her satırda kind, actor, actorSource (human|ai|system), occurredAt ve türe göre değişen bir summary vardır — merge.applied'da message ve counters, environment.created'da branchName, deployment.*'da süre ya da hata. subjectId merge satırlarında plan id'sidir; ayrıntı için merge_get ile okuyun. actorSource='ai' satırları bu gateway üzerinden yapılan işlerdir.",
30
+ description: "Projenin dal, merge ve dağıtım geçmişini en yeniden eskiye listeler: kim, ne zaman, ne yaptı. Her satırda kind, actor, actorSource (human|ai|system), occurredAt ve türe göre değişen bir summary vardır — merge.applied'da message ve counters, merge.resolutions_carried'da carried/dropped (tazelemede korunan ve yeniden sorulan karar sayısı), environment.created'da branchName, deployment.*'da süre ya da hata. subjectId merge satırlarında plan id'sidir; ayrıntı için merge_get ile okuyun. actorSource='ai' satırları bu gateway üzerinden yapılan işlerdir.",
29
31
  inputSchema: {
30
32
  projectId: guid,
31
33
  kind: activityKind,
@@ -2,6 +2,7 @@ import { guid, pagingParams, readOnly, writes, updates, destructive } from './co
2
2
  import { handle } from '../platform/responses.js';
3
3
  import { forgetApplication, readApplicationPlatform, resolveApplication } from '../knowledge/platform/resolve.js';
4
4
  import { mobileTabsProblems, readMobileTabs } from '../knowledge/platform/mobileTabs.js';
5
+ import { mobileTransitionProblems } from '../knowledge/platform/pageTransition.js';
5
6
  import { findingLine } from '../knowledge/platform/profile.js';
6
7
  import { listComponents } from './components.js';
7
8
  import { z } from 'zod';
@@ -37,6 +38,19 @@ export async function mobileTabsWarnings(platform, applicationId, projectSlug, h
37
38
  guide: "platform_guide('mobile') §Gezinme"
38
39
  }));
39
40
  }
41
+ /**
42
+ * MOB-029 (varsayılan sayfa geçişi) — `properties.mobile.transition` yazan mobil uygulama çağrısının denetimi.
43
+ * Yazım platforma YİNE gider (backend `properties`'i doğrulamaz); cihaz sözlük dışı değeri sessizce düşürdüğü için
44
+ * bulgunun söylendiği tek yer `platformWarnings`'tir.
45
+ */
46
+ export function mobileTransitionWarnings(properties) {
47
+ return mobileTransitionProblems(properties).map(message => findingLine({
48
+ code: 'MOB-029',
49
+ severity: 'warn',
50
+ message,
51
+ guide: "platform_guide('page') §Kural 1g-2"
52
+ }));
53
+ }
40
54
  function withPlatformWarnings(response, warnings) {
41
55
  if (warnings.length === 0 || !response || typeof response !== 'object' || Array.isArray(response))
42
56
  return response;
@@ -77,7 +91,7 @@ export function registerApplicationTools(server, platform) {
77
91
  annotations: writes
78
92
  }, handle(config, async ({ projectId, ...body }) => {
79
93
  const warnings = readApplicationPlatform(body.type) === 'mobile'
80
- ? await mobileTabsWarnings(platform, undefined, undefined, undefined, body.properties)
94
+ ? [...await mobileTabsWarnings(platform, undefined, undefined, undefined, body.properties), ...mobileTransitionWarnings(body.properties)]
81
95
  : [];
82
96
  const response = await platform.org('POST', `/applications/${projectId}`, { body });
83
97
  forgetApplication(platform);
@@ -100,7 +114,7 @@ export function registerApplicationTools(server, platform) {
100
114
  }, handle(config, async ({ id, ...body }) => {
101
115
  const current = await resolveApplication(platform, id);
102
116
  const warnings = current?.platform === 'mobile'
103
- ? await mobileTabsWarnings(platform, id, current.projectSlug, body.homepageId, body.properties)
117
+ ? [...await mobileTabsWarnings(platform, id, current.projectSlug, body.homepageId, body.properties), ...mobileTransitionWarnings(body.properties)]
104
118
  : [];
105
119
  const response = await platform.org('PUT', `/applications/${id}`, { body });
106
120
  forgetApplication(platform, id);
@@ -1,8 +1,8 @@
1
1
  import { guid, projectParam, environmentParam, readOnly, writes, updates, destructive } from './common.js';
2
- import { normalizeComponentDefaults, componentDefaultsNote, assertComponentBlocks, clampBlockSizes, blockSizeNote, normalizeHeightTypes, heightTypeNote, normalizeSizeStrings, sizeStringNote, normalizeDroppableStates, withNormalization } from '../knowledge/normalize.js';
2
+ import { normalizeComponentDefaults, assertNoRetiredNodes, componentDefaultsNote, assertComponentBlocks, clampBlockSizes, blockSizeNote, normalizeHeightTypes, heightTypeNote, normalizeSizeStrings, sizeStringNote, normalizeDroppableStates, withNormalization } from '../knowledge/normalize.js';
3
3
  import { eventContractsFor, eventPayloadNote } from '../knowledge/componentEvents.js';
4
4
  import { componentPresetDefaults, readProjectTheme } from '../knowledge/themeSchema.js';
5
- import { knowledgeFor, knowledgeNote, APPEARANCE_TOKEN_SETS, STATE_FLAG_NOTES, INTERNAL_ROLES_BY_TYPE } from '../knowledge/componentCatalog.js';
5
+ import { knowledgeFor, knowledgeNote, retiredComponent, APPEARANCE_TOKEN_SETS, STATE_FLAG_NOTES, INTERNAL_ROLES_BY_TYPE } from '../knowledge/componentCatalog.js';
6
6
  import { MOBILE_STATE_FLAG_NOTES, mobileComponentKnowledge } from '../knowledge/mobileComponentCatalog.js';
7
7
  import { mobileComponentEventContracts } from '../knowledge/mobileComponentEvents.js';
8
8
  import { componentUpdateBody, fetchHost, schemaParam, unwrap } from './screens.js';
@@ -184,7 +184,7 @@ export function registerComponentTools(server, platform) {
184
184
  const config = platform.config;
185
185
  server.registerTool('component_list', {
186
186
  title: 'Komponent sözlüğünü listele',
187
- description: "Sayfaya konabilecek kayıtlı komponent tiplerini (slug, name, kategori, droppable) kompakt listeler; tek tipin tam sözlüğü + davranış bilgisi için component_get, komponent modelinin geneli için platform_guide('component'). scope:'all' platformun varsayılan kataloğunu da getirir.",
187
+ description: "Sayfaya konabilecek kayıtlı komponent tiplerini (slug, name, kategori, droppable) kompakt listeler; retired: true taşıyan tip katalogdan kaldırılmıştır, yerine replacedBy kullanılır; tek tipin tam sözlüğü + davranış bilgisi için component_get, komponent modelinin geneli için platform_guide('component'). scope:'all' platformun varsayılan kataloğunu da getirir.",
188
188
  inputSchema: {
189
189
  project: projectParam,
190
190
  environment: environmentParam,
@@ -207,13 +207,16 @@ export function registerComponentTools(server, platform) {
207
207
  categories: component.categories,
208
208
  isDroppable: component.isDroppable,
209
209
  kind: component.kind,
210
- widgetType: component.widgetType
210
+ widgetType: component.widgetType,
211
+ ...(retiredComponent(typeof component.slug === 'string' ? component.slug : undefined)
212
+ ? { retired: true, replacedBy: retiredComponent(String(component.slug)).replacedBy }
213
+ : {})
211
214
  }))
212
215
  };
213
216
  }));
214
217
  server.registerTool('component_get', {
215
218
  title: 'Komponent sözlüğünü getir',
216
- description: "Tek komponent tipinin tam sözlüğü: prop'lar (tip/enum/default), event'ler (yükleriyle), davranış bilgisi (knowledge) ve boyut sınırları. Şemaya uydurma prop yazmayın — yalnız burada dönenleri kullanın. Yanıtta guide alanı dönerse derin rehberi platform_guide ile okuyun (örn. html). slug ya da id ile çağrılır.",
219
+ description: "Tek komponent tipinin tam sözlüğü: prop'lar (tip/enum/default), event'ler (yükleriyle), davranış bilgisi (knowledge) ve boyut sınırları. Şemaya uydurma prop yazmayın — yalnız burada dönenleri kullanın. Yanıtta guide alanı dönerse derin rehberi platform_guide ile okuyun (örn. html). Yanıtta retired alanı dönerse tip katalogdan kaldırılmıştır (kayıt defterinde eşitleme silmediği için durur): o tipi yeni düğümde kullanmayın, replacedBy'ı kullanın. slug ya da id ile çağrılır.",
217
220
  inputSchema: {
218
221
  project: projectParam,
219
222
  environment: environmentParam,
@@ -242,6 +245,7 @@ export function registerComponentTools(server, platform) {
242
245
  : [];
243
246
  const componentSlug = typeof component.slug === 'string' ? component.slug : undefined;
244
247
  const knowledge = knowledgeFor(componentSlug);
248
+ const retired = retiredComponent(componentSlug);
245
249
  // Mobil uygulamada kayıt mobil kataloğun satırıdır (`listComponents` type=mobile); davranış
246
250
  // notları web runtime'ından gelir (`knowledge` — mobil şema web sınıfını genişletir), bileşenin
247
251
  // mobil farkları `mobileKnowledge`'ta, bütün mobil bileşenlere geçen notlar `mobileNotes`'ta (G4).
@@ -262,6 +266,7 @@ export function registerComponentTools(server, platform) {
262
266
  slug: component.slug,
263
267
  name: component.name,
264
268
  description: component.description,
269
+ ...(retired ? { retired } : {}),
265
270
  kind: component.kind,
266
271
  widgetType: component.widgetType,
267
272
  isDroppable: component.isDroppable,
@@ -282,7 +287,7 @@ export function registerComponentTools(server, platform) {
282
287
  ...(componentSlug && INTERNAL_ROLES_BY_TYPE[componentSlug]
283
288
  ? {
284
289
  internalRoles: INTERNAL_ROLES_BY_TYPE[componentSlug],
285
- internalRolesNote: "Bu komponentin İÇ kontrolleri dışarıdan adreslenebilir: page_screenshot actions hedefine '<elementId>__<rol>' yazın (ör. 'tbl_vacancy__pager-next'). Kimlikler runtime'ın data-element-id özniteliğidir; ikinci bir mekanizma yoktur."
290
+ internalRolesNote: "Bu komponentin İÇ kontrolleri dışarıdan adreslenebilir: page_screenshot actions hedefine '<elementId>__<rol>' yazın (ör. 'tabs_ayar__tab-genel'). Kimlikler runtime'ın data-element-id özniteliğidir; ikinci bir mekanizma yoktur."
286
291
  }
287
292
  : {}),
288
293
  ...(themeDefaults ? { themeDefaults } : {}),
@@ -322,6 +327,7 @@ export function registerComponentTools(server, platform) {
322
327
  if (body.kind === 'widget' && !body.widgetType) {
323
328
  throw new PlatformApiError("kind:'widget' için widgetType (container | modal | drawer) zorunludur.");
324
329
  }
330
+ assertNoRetiredNodes(body.schema);
325
331
  const wrapped = body.kind === 'widget' && body.widgetType && (body.schema ?? []).length > 0
326
332
  ? wrapWidgetRoot(body.schema ?? [], body.widgetType, await listComponents(platform, project, environment, applicationId).catch(() => []))
327
333
  : { schema: body.schema, notes: [] };
@@ -1,5 +1,5 @@
1
- import { orphanedFlowWarnings, orphanedFlowPlan, orphanedFlowMessage, boundEventOps, stripGatewayEventFields, staleSetterWarnings, normalizeSchemaOps, assertAddComponentIds, validationNote, componentNameNote, componentDefaultsNote, replacePropNote, flexSizeWarnings, nestedRepeaterNodes, nestedRepeaterMessage, stringStyleNodes, stringStyleWarning, inertFlexPropNodes, inertFlexPropWarning, verticalGapWarnings, withNormalization, FLEX_LAYOUT_PROP_NAMES } from '../knowledge/normalize.js';
2
- import { opsParam, writeViewParam, outlineWriteResponse, unknownEventOps, collectTypes, styleTargetTypesFrom, simulateLayoutOps, removeNodeFromTree, planEventRemovals, planResizes, planPropTypes, assertOpBodies, applySkeletonOps, reshapesSkeleton } from './schemaOps.js';
1
+ import { orphanedFlowWarnings, orphanedFlowPlan, orphanedFlowMessage, boundEventOps, stripGatewayEventFields, staleSetterWarnings, normalizeSchemaOps, assertAddComponentIds, assertNoRetiredTypes, validationNote, componentNameNote, componentDefaultsNote, replacePropNote, flexSizeWarnings, nestedRepeaterNodes, nestedRepeaterMessage, stringStyleNodes, stringStyleWarning, inertFlexPropNodes, inertFlexPropWarning, verticalGapWarnings, withNormalization, FLEX_LAYOUT_PROP_NAMES } from '../knowledge/normalize.js';
2
+ import { opsParam, writeViewParam, outlineWriteResponse, unknownEventOps, collectTypes, simulateLayoutOps, removeNodeFromTree, planEventRemovals, callerSlots, planEmulatedOps, recordsWithEventRemovals, serverOpsInOrder, assertOpBodies, applySkeletonOps, reshapesSkeleton } from './schemaOps.js';
3
3
  import { guid, projectParam, environmentParam, updates, microOps } from './common.js';
4
4
  import { fetchHost, hostPath } from './screens.js';
5
5
  import { assertScreenWriteGuards, screenCatalogNotes } from './screenWrite.js';
@@ -10,6 +10,7 @@ import { PlatformApiError } from '../platform/errors.js';
10
10
  import { connectDataOpIssues, connectedPropKeys } from '../knowledge/connectDataDefinition.js';
11
11
  import { handle } from '../platform/responses.js';
12
12
  import { slotWarning } from './layouts.js';
13
+ import { batchEnvelopeNote, openBatchEnvelopes } from '../knowledge/platform/batchOps.js';
13
14
  import { applicationTypeField, resolveProfile } from '../knowledge/platform/resolve.js';
14
15
  import { boundFlowSteps, layoutPatchWarnings, screenProfileWarnings } from './profileWrite.js';
15
16
  import { z } from 'zod';
@@ -57,12 +58,14 @@ export function registerDesignSchemaPatchTool(server, platform, host) {
57
58
  },
58
59
  annotations: updates
59
60
  }, handle(config, async ({ project, environment, applicationId, id, ops, operations, allowFlowDeletion, view }) => {
60
- const allOps = microOps(spec.tool, ops, operations).map(op => op);
61
+ const opened = openBatchEnvelopes(spec.tool, microOps(spec.tool, ops, operations).map(op => op));
62
+ const allOps = opened.ops;
61
63
  const removeEventOps = allOps.filter(entry => entry.op === 'remove_event');
62
64
  const resizeOps = allOps.filter(entry => entry.op === 'resize_component');
63
65
  const propTypeOps = allOps.filter(entry => entry.op === 'set_prop_type');
64
66
  const opRecords = allOps.filter(entry => !['remove_event', 'resize_component', 'set_prop_type'].includes(String(entry.op)));
65
67
  assertAddComponentIds(opRecords);
68
+ assertNoRetiredTypes(opRecords);
66
69
  const record = await fetchHost(platform, host, project, environment, applicationId, id);
67
70
  // Platform ucu kaydın türüne bakmaz: sıradan bir komponentin id'si verilirse kataloğundaki
68
71
  // tanımın yanına bir şema yazılırdı.
@@ -78,15 +81,13 @@ export function registerDesignSchemaPatchTool(server, platform, host) {
78
81
  const sectionToggles = expandSectionToggles(opRecords, nodes, sectionIds);
79
82
  const sectionNotes = boxSectionNote([...createdSections, ...sectionToggles.created], sectionToggles.removed);
80
83
  const events = host === 'layout' ? record.events : undefined;
81
- // Normalizasyon kayıttan SONRA koşar: stil anahtarı çevirisi hedefin komponent TİPİNE bakar
82
- // (TB-129, table.style.th/td kebab-case kalır) ve tip yalnız okunan ağaçta var. Gövde hataları
83
- // bu yüzden bir okuma sonrasında rapor edilir; karar hiç değişmez, yalnız sıra değişti.
84
- const normalized = normalizeSchemaOps(opRecords, styleTargetTypesFrom(opRecords, nodes));
84
+ const normalized = normalizeSchemaOps(opRecords);
85
85
  assertOpBodies(opRecords, propTypeOps);
86
86
  // CD-005 layout ve widget host'unda da: bağlı prop'u (iç içe yol dahil) ezen yazma ve bağlı olmayan prop'ta
87
87
  // remove_connect_data okunan ağaca bakarak reddedilir (gövde denetimi assertOpBodies'te koştu).
88
88
  const connectedProps = connectedPropKeys(nodes);
89
- const connectionIssues = opRecords.flatMap(entry => connectDataOpIssues(entry, connectedProps).map(issue => `${issue.code} — ${issue.message}`));
89
+ // `set_prop_type` ayrı listededir; bağlı prop'u ezen yazımı o da yapar (sayfa yamasıyla aynı kapı).
90
+ const connectionIssues = [...opRecords, ...propTypeOps].flatMap(entry => connectDataOpIssues(entry, connectedProps).map(issue => `${issue.code} — ${issue.message}`));
90
91
  if (connectionIssues.length > 0)
91
92
  throw new PlatformApiError(`Connect Data op gövdesi geçersiz (istek gönderilmedi): ${connectionIssues.join(' · ')}`, 400);
92
93
  if (opRecords.some(entry => entry.op === 'set_event' || entry.op === 'add_event')) {
@@ -102,7 +103,9 @@ export function registerDesignSchemaPatchTool(server, platform, host) {
102
103
  throw new PlatformApiError(`PAGE-008 — geçersiz olay adı (istek gönderilmedi): ${unknown.join(' · ')}`, 400);
103
104
  }
104
105
  stripGatewayEventFields(opRecords);
105
- const deletedFlows = orphanedFlowPlan(nodes, events, [...opRecords, ...removeEventOps]);
106
+ // Bölüm anahtarı op'larını yukarıda ekledi; buradan sonra `opRecords` yeni op almaz.
107
+ const slots = callerSlots(allOps, opRecords);
108
+ const deletedFlows = orphanedFlowPlan(nodes, events, recordsWithEventRemovals(slots, opRecords));
106
109
  if (deletedFlows.length > 0 && allowFlowDeletion !== true)
107
110
  throw new PlatformApiError(orphanedFlowMessage(deletedFlows), 400);
108
111
  const added = opRecords.filter(entry => entry.op === 'add_component').map(entry => entry.component);
@@ -120,7 +123,7 @@ export function registerDesignSchemaPatchTool(server, platform, host) {
120
123
  // her yamada koşsaydı kayıtlı ağacında zaten iç içe repeater olan layout hiç yamalanamazdı.
121
124
  const simulatable = opRecords.some(entry => ['add_component', 'move_component'].includes(String(entry.op))
122
125
  || (entry.op === 'replace_prop' && FLEX_LAYOUT_PROP_NAMES.has(String(entry.prop ?? ''))));
123
- const nested = simulatable ? nestedRepeaterNodes(simulated) : [];
126
+ const nested = simulatable ? nestedRepeaterNodes(simulated, resolved.profile.platform) : [];
124
127
  if (nested.length > 0)
125
128
  throw new PlatformApiError(nestedRepeaterMessage(nested, resolved.profile.platform), 400);
126
129
  const reshaped = simulatable || opRecords.some(entry => entry.op === 'remove_component');
@@ -145,9 +148,10 @@ export function registerDesignSchemaPatchTool(server, platform, host) {
145
148
  layoutWarnings.push(...layoutPatchWarnings(resolved, nodes, skeleton));
146
149
  }
147
150
  const removedEvents = planEventRemovals(removeEventOps);
148
- const resized = resizeOps.length > 0 ? planResizes(nodes, resizeOps, spec.readTool) : { serverOps: [], applied: [], warnings: [] };
149
- const propTypes = propTypeOps.length > 0 ? planPropTypes(nodes, propTypeOps, spec.readTool) : { serverOps: [], applied: [] };
150
- const serverOps = [...removeEventOps, ...resized.serverOps, ...propTypes.serverOps, ...opRecords];
151
+ // Sayfa yamasıyla aynı iki söz (`pages.ts` `planPageWrite`, PAGE-050): öykünülen op ağacın o andaki hâline göre
152
+ // planlanır ve liste platforma çağıranın sırasıyla gider.
153
+ const { resized, propTypes } = planEmulatedOps(nodes, slots, opRecords, spec.readTool);
154
+ const serverOps = serverOpsInOrder(slots, { sent: opRecords, resized: resized.groups, propTypes: propTypes.serverOps });
151
155
  const response = ((await platform.tenant('PATCH', project, `${hostPath(host, applicationId, id)}/schema`, {
152
156
  environment,
153
157
  body: { ops: serverOps }
@@ -181,6 +185,7 @@ export function registerDesignSchemaPatchTool(server, platform, host) {
181
185
  }
182
186
  }
183
187
  return withWriteDiff(withNormalization(response, [
188
+ ...batchEnvelopeNote(opened),
184
189
  ...componentNameNote(normalized.names),
185
190
  ...sectionNotes,
186
191
  ...componentDefaultsNote(normalized.components),
@@ -26,6 +26,40 @@ function preserveApplicationBinding(definition) {
26
26
  if (applicationIds.length > 0)
27
27
  definition.applicationIds = applicationIds;
28
28
  }
29
+ /**
30
+ * 37 S-1: platform PATCH op'larını `EntityDto` OKUMA şekline uygular (`PatchEntityCommandHandler` →
31
+ * `UpdateEntityCommand.FromDto`, `ApplicationIds = Applications.Select(a => a.ApplicationId)`). Bağ
32
+ * `/applications` yolunda `{ applicationId }` öğeleriyle yazılır; `/applicationIds` belgede yoktur
33
+ * (gateway önizlemesi `OPERATION_PATH_UNRESOLVABLE` ile düşer), çıplak guid 500 `JsonException`,
34
+ * `{ id }` boş `applicationId` ile "This record is in use" verir. Ajan yazma adını bilerek geldiği
35
+ * için reddetmek yerine çevrilir ve `gatewayNormalized` ile söylenir (microservice `/roleIds` emsali).
36
+ */
37
+ function normalizeApplicationBindingOps(operations) {
38
+ const notes = [];
39
+ const asBinding = (entry) => {
40
+ if (typeof entry === 'string')
41
+ return { applicationId: entry };
42
+ if (entry && typeof entry === 'object' && !('applicationId' in entry) && typeof entry.id === 'string')
43
+ return { applicationId: entry.id };
44
+ return entry;
45
+ };
46
+ for (const operation of operations) {
47
+ const path = typeof operation.path === 'string' ? operation.path : '';
48
+ if (path === '/applicationIds' || path.startsWith('/applicationIds/')) {
49
+ operation.path = `/applications${path.slice('/applicationIds'.length)}`;
50
+ notes.push(`patch yolu '${path}' → '${operation.path}': yamalanan belge entity_get'in OKUMA şeklidir (EntityDto) ve uygulama bağı orada 'applications' [{ applicationId }] alanıdır; 'applicationIds' yalnız create/update GÖVDESİNDE vardır.`);
51
+ }
52
+ const target = operation.path;
53
+ if (target !== '/applications' && !target.startsWith('/applications/'))
54
+ continue;
55
+ const before = JSON.stringify(operation.value);
56
+ operation.value = target === '/applications' && Array.isArray(operation.value) ? operation.value.map(asBinding) : target.split('/').length === 3 ? asBinding(operation.value) : operation.value;
57
+ if (JSON.stringify(operation.value) !== before) {
58
+ notes.push(`'${target}' değeri { applicationId } öğesine çevrildi — çalışan şekil: { op: 'replace', path: '/applications', value: [{ applicationId: '<application id>' }] }.`);
59
+ }
60
+ }
61
+ return notes;
62
+ }
29
63
  /**
30
64
  * `UpdateEntityCommandHandler` gövdedeki `Indexes` null ise onu BOŞ LİSTEYE çeviriyor
31
65
  * (`request.Indexes?…ToList() ?? []`) ve `Entity.FindProcessedIndexes` boş listede entity'nin
@@ -178,7 +212,7 @@ export function registerEntityTools(server, platform) {
178
212
  }));
179
213
  server.registerTool('entity_patch', {
180
214
  title: 'Entity\'ye patch uygula',
181
- description: "YENİ alanı korumayla ya da türetilmiş alan olarak eklemek için de kullanılır (PROT-003/PROT-004); eklenen ya da kuralı değişen türetilmiş alanı dolu satırlarda derived-fill işi doldurur (tablo kilitlenmez, bitiş import_history { entityId }). VAR OLAN alanın korumasını değiştirmez — platform 400 ProtectionChangeNotAllowed döner, gateway istek göndermeden durdurur ve yol entity_protection_update'tir. Tam gövdeyi bağlama almadan hedefli değişiklik: gateway mevcut tanımı okur, yolları çözer ve op'ları platformun PATCH /entities/{id} ucuna `{ baseVersion, ops }` olarak geçirir — tam gövde PUT'u ATILMAZ, yalnız fark yazılır. Yanıt outline özetidir. YOLLARDA SEÇİCİ KULLANIN, indeks değil: '/fields[slug=email]/nullable' — indeksle yazmak alan sırası değişince SESSİZCE yanlış alanı yazar. Gateway seçiciyi indekse çevirir ve hangi kayda indiğini yanıttaki patchTargets ile söyler. `baseVersion` okunan kaydın `version` alanından gelir; kayıt bu arada değiştiyse platform 409 döner ve HİÇBİR op uygulanmaz. 'applications' → 'applicationIds' çevirisini artık platform yapar. `fields[].isLocalized: true` alanı ana tablodan çıkarıp companion `<slug>_localization` entity'sine taşır (yalnız düz METİN alanı; anahtar/unique/ilişki olamaz) — platform_guide('entity') §'Çoklu dilli alan'.",
215
+ description: "YENİ alanı korumayla ya da türetilmiş alan olarak eklemek için de kullanılır (PROT-003/PROT-004); eklenen ya da kuralı değişen türetilmiş alanı dolu satırlarda derived-fill işi doldurur (tablo kilitlenmez, bitiş import_history { entityId }). VAR OLAN alanın korumasını değiştirmez — platform 400 ProtectionChangeNotAllowed döner, gateway istek göndermeden durdurur ve yol entity_protection_update'tir. Tam gövdeyi bağlama almadan hedefli değişiklik: gateway mevcut tanımı okur, yolları çözer ve op'ları platformun PATCH /entities/{id} ucuna `{ baseVersion, ops }` olarak geçirir — tam gövde PUT'u ATILMAZ, yalnız fark yazılır. Yanıt outline özetidir. YOLLARDA SEÇİCİ KULLANIN, indeks değil: '/fields[slug=email]/nullable' — indeksle yazmak alan sırası değişince SESSİZCE yanlış alanı yazar. Gateway seçiciyi indekse çevirir ve hangi kayda indiğini yanıttaki patchTargets ile söyler. `baseVersion` okunan kaydın `version` alanından gelir; kayıt bu arada değiştiyse platform 409 döner ve HİÇBİR op uygulanmaz. UYGULAMA BAĞI '/applications' yolundadır ve öğeleri { applicationId } nesneleridir: {op:'replace', path:'/applications', value:[{applicationId:'<application id>'}]} — tam listeyi yazar; tek bağ eklemek {op:'add', path:'/applications/-', value:{applicationId:'<application id>'}}. '/applicationIds' yolunu ya da çıplak guid / { id } öğesini gateway bu şekle çevirir ve gatewayNormalized ile söyler; 'applications' → 'applicationIds' gövde çevirisini platform yapar. `fields[].isLocalized: true` alanı ana tablodan çıkarıp companion `<slug>_localization` entity'sine taşır (yalnız düz METİN alanı; anahtar/unique/ilişki olamaz) — platform_guide('entity') §'Çoklu dilli alan'.",
182
216
  inputSchema: { project: projectParam, environment: environmentParam, datasourceId: guid.optional().describe('Verilmezse gateway datasource ağacından bulur'), id: entityRef, operations: patchOperationsParam.optional(), ops: patchOperationsParam.optional().describe("'operations' ile aynı alan (eşanlamlı) — sayfa micro-op tool'larıyla aynı adı kullanabilmeniz için"), policyOverride: policyOverrideParam },
183
217
  annotations: updates
184
218
  }, handle(config, async ({ project, environment, datasourceId, id, operations: givenOperations, ops, policyOverride }) => {
@@ -187,7 +221,10 @@ export function registerEntityTools(server, platform) {
187
221
  const entity = datasourceId && isGuid(id) ? { entityId: id, datasourceId } : await resolver.entity(id);
188
222
  const current = await platform.tenant('GET', project, `/entities/${entity.datasourceId}/${entity.entityId}`, { environment });
189
223
  const definition = structuredClone((current?.data ?? current));
190
- const resolvedOps = resolvePatchOperations(definition, operations, 'entity_get');
224
+ // Bağ yolu seçici çözümünden ÖNCE çevrilir: '/applicationIds' belgede yoktur (37 S-1).
225
+ const bindingOperations = structuredClone(operations);
226
+ const bindingNotes = normalizeApplicationBindingOps(bindingOperations);
227
+ const resolvedOps = resolvePatchOperations(definition, bindingOperations, 'entity_get');
191
228
  // Politika, yazmadan önce sonucu görmeli; yerel kopya YALNIZ değerlendirme için, gövde olarak gönderilmez.
192
229
  const preview = fastJsonPatch.applyPatch(definition, resolvedOps.operations, true, false).newDocument;
193
230
  const protectionChanges = changedExistingProtections(current?.data ?? current, preview);
@@ -208,7 +245,7 @@ export function registerEntityTools(server, platform) {
208
245
  });
209
246
  const written = patchResult(saved, preview);
210
247
  const touched = patchedSections(written.data, resolvedOps.operations);
211
- return attachFindings(withUnmappedMembers(withResolved({
248
+ return attachFindings(withUnmappedMembers(withResolved(withNormalization({
212
249
  applied: operations.length,
213
250
  newVersion: written.newVersion,
214
251
  patchTargets: resolvedOps.targets,
@@ -216,7 +253,7 @@ export function registerEntityTools(server, platform) {
216
253
  // S-8: yazılanın kanıtı yanıtta dursun; dokunulmayan bölümler outline'da kalır.
217
254
  ...(Object.keys(touched).length > 0 ? { touched } : {}),
218
255
  outline: entityOutline(written.data)
219
- }, resolver.resolved), drift), findings);
256
+ }, bindingNotes), resolver.resolved), drift), findings);
220
257
  }));
221
258
  server.registerTool('entity_protection_update', {
222
259
  title: 'Alan korumasını değiştir',
@@ -2,8 +2,8 @@ import { guid, projectParam, environmentParam, pagingParams, readOnly, writes, u
2
2
  import { resolvePatchOperations } from '../knowledge/outlines.js';
3
3
  import { patchResult, baseVersionOf } from '../platform/patch.js';
4
4
  import fastJsonPatch from 'fast-json-patch';
5
- import { fillComponentNames, componentNameNote, normalizeComponentDefaults, componentDefaultsNote, flexSizeWarnings, spacingLayerWarnings, verticalGapWarnings, withNormalization } from '../knowledge/normalize.js';
6
- import { fetchHost, layoutUpdateBody, schemaParam, eventsParam, modeParam, summarize, findNode } from './screens.js';
5
+ import { fillComponentNames, componentNameNote, normalizeComponentDefaults, assertNoRetiredNodes, componentDefaultsNote, flexSizeWarnings, spacingLayerWarnings, verticalGapWarnings, withNormalization } from '../knowledge/normalize.js';
6
+ import { fetchHost, layoutUpdateBody, schemaParam, eventsParam, modeParam, summarize, findNode, readFullSchema } from './screens.js';
7
7
  import { assertScreenWriteGuards, assertScreenCatalog } from './screenWrite.js';
8
8
  import { applyPropTypesToSchema, isSetPropTypeOp, setPropTypeErrors } from '../knowledge/propTypes.js';
9
9
  import { writeDiff, withWriteDiff } from '../platform/writeDiff.js';
@@ -15,7 +15,7 @@ import { jsonArray } from '../platform/jsonArray.js';
15
15
  import { z } from 'zod';
16
16
  const patchOperationsParam = jsonArray(z.array(z.record(z.unknown())).min(1))
17
17
  .describe("JSON Patch listesi (RFC 6902): { op, path, value }. Yol seçici alabilir — '/views/0/children[elementId=baslik]/props/content/data'.");
18
- const viewsParam = schemaParam.describe("Layout'un komponent ağacı: her düğümde elementId, name (= elementId), type, blocks zorunlu — blocks: [{ breakpoint: 1024, size: { w, h }, position: { x, y } }], gateway doldurmaz (PAGE-037). DİKKAT: layout'ta şema alanının adı 'schema' değil 'views'tir; ağaçta hedefli değişiklik için tam ağaç yerine layout_schema_patch micro-op'larını kullanın. İçinde MUTLAKA bir { type: 'slot' } komponenti olmalı — sayfa içeriği YALNIZ slot'un içine render edilir, slot'suz layout'a bağlanan sayfalar boş görünür. 2026-09-08'den beri views, page_schema_patch'in doğrulama duvarından geçer: tanınmayan prop adı ve geçersiz enum değeri 422 ile REDDEDİLİR (kabul edilen değer listesiyle) ve hiçbir şey kaydedilmez — eskiden sessizce kaydediliyordu (P1-2).");
18
+ const viewsParam = schemaParam.describe("Layout'un komponent ağacı: her düğümde elementId, name (= elementId), type, blocks zorunlu — blocks: [{ breakpoint: 1024, size: { w, h }, position: { x, y } }], gateway doldurmaz (PAGE-037). DİKKAT: layout'ta şema alanının adı 'schema' değil 'views'tir; ağaçta hedefli değişiklik için tam ağaç yerine layout_schema_patch micro-op'larını kullanın. İçinde MUTLAKA bir { type: 'slot' } komponenti olmalı — sayfa içeriği YALNIZ slot'un içine render edilir, slot'suz layout'a bağlanan sayfalar boş görünür. 2026-09-08'den beri views, page_schema_patch'in doğrulama duvarından geçer: tanınmayan prop adı ve geçersiz enum değeri 422 ile REDDEDİLİR (kabul edilen değer listesiyle) ve hiçbir şey kaydedilmez — eskiden sessizce kaydediliyordu (P1-2). layout_create'te katalogdan kaldırılan tipte düğüm (component_list'te retired: true) PAGE-051 ile reddedilir.");
19
19
  function countSlots(nodes) {
20
20
  if (!Array.isArray(nodes))
21
21
  return 0;
@@ -58,24 +58,39 @@ export function registerLayoutTools(server, platform) {
58
58
  }));
59
59
  server.registerTool('layout_schema_get', {
60
60
  title: "Layout komponent ağacını oku",
61
- description: "Layout'un komponent ağacını (views) okur — page_schema_get'in layout karşılığı. Ağaçta hedefli değişiklik için layout_schema_patch (sayfa ile aynı micro-op sözlüğü, PATCH /layouts/{app}/{id}/schema). Ad/parametre/olay alanlarında ya da düz JSON Patch yolu gerektiğinde layout_patch — o araç buradaki updatedAt'i expectedUpdatedAt olarak kabul eder. Tam ağacı kendiniz yazacaksanız layout_update.",
61
+ description: "Layout'un komponent ağacını (views) okur — page_schema_get'in layout karşılığı. Ağaçta hedefli değişiklik için layout_schema_patch (sayfa ile aynı micro-op sözlüğü, PATCH /layouts/{app}/{id}/schema). Ad/parametre/olay alanlarında ya da düz JSON Patch yolu gerektiğinde layout_patch — o araç buradaki updatedAt'i expectedUpdatedAt olarak kabul eder. Tam ağacı kendiniz yazacaksanız layout_update. full ve subtree yanıtı nodeCount taşır; full tek yanıta sığmazsa HATA döner (sessiz kırpma yok): cursor:'0' ile parça parça okuyun — page_schema_get ile aynı sözleşme (items[] { parentElementId, index, node }, nextCursor bitene kadar; layout okuma arasında değişirse 409). allowTruncated:true kırpılmış gövdeyi açıkça verir — o gövdeyi layout_update'e geri yazmayın.",
62
62
  inputSchema: {
63
63
  project: projectParam,
64
64
  environment: environmentParam,
65
65
  applicationId: guid,
66
66
  id: guid,
67
- mode: modeParam,
68
- componentId: z.string().optional().describe("subtree kipinde kök alınacak komponentin elementId'si")
67
+ mode: modeParam.describe("summary (varsayılan): yalnız id/elementId/name/type ağacı — hedef bulmak için bunu kullanın. subtree: componentId ile tek alt ağaç. full: tüm views; tek yanıta sığmazsa HATA döner — cursor:'0' ile parça parça okuyun (nextCursor bitene kadar) ya da allowTruncated:true ile kırpılmış gövdeyi açıkça isteyin."),
68
+ componentId: z.string().optional().describe("subtree kipinde kök alınacak komponentin elementId'si"),
69
+ cursor: z.string().min(1).optional().describe("Yalnız mode:'full': parçalı okuma. İlk çağrıda '0', sonra yanıttaki nextCursor; nextCursor gelmeyince views bitmiştir. Layout okuma arasında değişirse (updatedAt) 409 döner — cursor:'0' ile baştan başlayın."),
70
+ allowTruncated: z.boolean().optional().describe("Yalnız mode:'full': true verilirse sığmayan views hata yerine kırpılarak döner (nodeCount toplamı söyler, düşen düğümler söylenmez). Varsayılan false.")
69
71
  },
70
72
  annotations: readOnly
71
- }, handle(config, async ({ project, environment, applicationId, id, mode, componentId }) => {
73
+ }, handle(config, async ({ project, environment, applicationId, id, mode, componentId, cursor, allowTruncated }) => {
72
74
  const layout = await fetchHost(platform, 'layout', project, environment, applicationId, id);
73
75
  const typeField = applicationTypeField(await resolveProfile(platform, applicationId));
74
76
  const audit = layout.audit;
75
77
  const updatedAt = audit?.updatedAt ?? audit?.createdAt;
76
78
  const views = (layout.views ?? []);
77
- if (mode === 'full')
78
- return { updatedAt, ...typeField, views };
79
+ if (cursor !== undefined && mode !== 'full') {
80
+ throw new PlatformApiError(`cursor yalnız mode:'full' ile çalışır (verilen mode: '${mode ?? 'summary'}'); parçalı okuma tüm views içindir. mode:'full' ile cursor:'0' verin ya da cursor'ı kaldırın.`, 400);
81
+ }
82
+ if (mode === 'full') {
83
+ return readFullSchema({
84
+ noun: 'Layout',
85
+ field: 'views',
86
+ nodes: views,
87
+ versionKey: String(updatedAt ?? ''),
88
+ base: { updatedAt, ...typeField },
89
+ cursor,
90
+ allowTruncated,
91
+ maxResponseBytes: config.maxResponseBytes
92
+ });
93
+ }
79
94
  if (mode === 'subtree') {
80
95
  if (!componentId)
81
96
  throw new PlatformApiError("mode='subtree' için componentId zorunludur.");
@@ -103,6 +118,7 @@ export function registerLayoutTools(server, platform) {
103
118
  const resolved = await resolveProfile(platform, applicationId);
104
119
  const named = fillComponentNames(views);
105
120
  assertScreenWriteGuards('layout', { schema: views, events }, resolved.profile.platform);
121
+ assertNoRetiredNodes(views);
106
122
  const fixed = normalizeComponentDefaults(views);
107
123
  const catalogNotes = await assertScreenCatalog(platform, project, environment, applicationId, views, 'layout_create');
108
124
  const gaps = [
@@ -212,6 +228,10 @@ export function registerLayoutTools(server, platform) {
212
228
  const profile = await resolveProfile(platform, applicationId);
213
229
  const named = fillComponentNames(overrides.views);
214
230
  assertScreenWriteGuards('layout', { schema: overrides.views, events: overrides.events }, profile.profile.platform);
231
+ // PAGE-051: ağacın tamamı değil yalnız yamanın EKLEDİĞİ düğümler — kayıtlı emekli düğüm yazılabilir kalır.
232
+ assertNoRetiredNodes(resolved.operations
233
+ .filter(op => op.op === 'add' && /\/(views|children)\/(\d+|-)$/.test(String(op.path ?? '')))
234
+ .map(op => op.value));
215
235
  const fixed = normalizeComponentDefaults(overrides.views);
216
236
  const catalogNotes = await assertScreenCatalog(platform, project, environment, applicationId, overrides.views, 'layout_patch');
217
237
  const body = layoutUpdateBody({ ...current, applicationId }, overrides);