@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.
- package/dist/knowledge/componentCatalog.js +95 -104
- package/dist/knowledge/componentEvents.js +34 -18
- package/dist/knowledge/mobileComponentCatalog.js +119 -20
- package/dist/knowledge/mobileComponentEvents.js +41 -0
- package/dist/knowledge/motionResolve.js +252 -0
- package/dist/knowledge/normalize.js +117 -71
- package/dist/knowledge/pageDiscover.js +2 -2
- package/dist/knowledge/platform/batchOps.js +70 -0
- package/dist/knowledge/platform/heroTag.js +153 -0
- package/dist/knowledge/platform/lint.js +128 -6
- package/dist/knowledge/platform/mobile.js +1 -1
- package/dist/knowledge/platform/mobileLayoutLint.js +8 -1
- package/dist/knowledge/platform/mobileRules.js +18 -0
- package/dist/knowledge/platform/pageTransition.js +57 -0
- package/dist/knowledge/platform/web.js +1 -1
- package/dist/knowledge/rules.js +217 -24
- package/dist/knowledge/styleResolve.js +0 -1
- package/dist/knowledge/styleResolveNative.js +88 -4
- package/dist/platform/capture/actions.js +27 -1
- package/dist/platform/capture/capture.js +10 -13
- package/dist/platform/capture/persistent-browser.js +130 -0
- package/dist/platform/capture/summary.js +4 -1
- package/dist/resources/examples/flow-list-to-form.json +8 -10
- package/dist/resources/examples/flow-load-table.json +1 -1
- package/dist/resources/examples/mobile-app-shell-drawer.json +1 -0
- package/dist/resources/examples/mobile-list-detail-form.json +7 -3
- package/dist/resources/examples/mobile-scan-code.json +285 -0
- package/dist/resources/guides/component.md +3 -3
- package/dist/resources/guides/entity.md +16 -0
- package/dist/resources/guides/expressions.md +8 -7
- package/dist/resources/guides/merge.md +98 -18
- package/dist/resources/guides/microservice.md +3 -3
- package/dist/resources/guides/middleware.md +2 -0
- package/dist/resources/guides/mobile-layout.md +2 -1
- package/dist/resources/guides/mobile-publish.md +1 -0
- package/dist/resources/guides/mobile-steps.md +67 -0
- package/dist/resources/guides/mobile.md +51 -70
- package/dist/resources/guides/page.md +229 -137
- package/dist/resources/guides/translation.md +67 -1
- package/dist/resources/guides/workflow.md +31 -1
- package/dist/resources/schema-fingerprint.json +2 -2
- package/dist/resources/schemas/ResolveMergeConflictsCommand.json +30 -1
- package/dist/tools/activities.js +3 -1
- package/dist/tools/applications.js +16 -2
- package/dist/tools/components.js +12 -6
- package/dist/tools/designSchemaPatch.js +18 -13
- package/dist/tools/entities.js +41 -4
- package/dist/tools/layouts.js +29 -9
- package/dist/tools/merges.js +29 -9
- package/dist/tools/mobile.js +15 -1
- package/dist/tools/pages.js +156 -168
- package/dist/tools/platformInfo.js +3 -2
- package/dist/tools/profileWrite.js +2 -2
- package/dist/tools/queries.js +1 -1
- package/dist/tools/rows.js +14 -3
- package/dist/tools/schemaOps.js +132 -34
- package/dist/tools/screenWrite.js +3 -3
- package/dist/tools/screens.js +42 -3
- package/dist/tools/translations.js +148 -6
- 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
|
|
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": "
|
|
3
|
-
"generatedAt": "2026-10-
|
|
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
|
}
|
package/dist/tools/activities.js
CHANGED
|
@@ -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);
|
package/dist/tools/components.js
CHANGED
|
@@ -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. '
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
const
|
|
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),
|
package/dist/tools/entities.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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',
|
package/dist/tools/layouts.js
CHANGED
|
@@ -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
|
|
78
|
-
|
|
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);
|