@appaflytech/zeyrek-ai-mcp 0.0.32 → 0.0.34

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.
@@ -134,7 +134,7 @@ export const componentKnowledge = {
134
134
  behavior: [
135
135
  LAYOUT_ABSENT_NOTE,
136
136
  "GÖNDERİM: submit yalnız bütün kurallar geçince yayılır; kalırsa alanlar işaretlenir, liste dolar ve sayfa listeye kayar. Alan mesajları formun İLK gönderiminden sonra görünür, o andan itibaren her change o alanı canlı yeniden doğrular. Formu gönderen düğme Type='submit' olur (form içinde ya da formId ile bağlı); akışta event.source.id gönderen düğmenin ADI, event.source.props onun prop'larıdır — event.data boştur (componentEvents).",
137
- "value ve isValid RUNTIME YAZAR, panelde çizilmez (2026-09-18): Page.components.<form>.value = { <alan adı>: değer } — yalnız doğrulama sözlüğündeki alan bileşenleri girer (text/button gibi gösterim düğümleri girmez), boş alan null, aralık seçici { valueFrom, valueTo }; gönderim akışında self.value ile de okunur ve uca olduğu gibi verilebilir. Page.components.<form>.isValid mount'ta ve her değişimde SESSİZCE yeniden hesaplanır (gizli alanlar muaf, form düzeyi kurallar dahil) — mesaj göstermez; submit düğmesini {{ !Page.components.<form>.isValid }} ile kilitlemenin yolu budur. İkisini setter ile YAZMA, sonraki değişimde ezilir.",
137
+ "value ve isValid RUNTIME YAZAR, panelde çizilmez (2026-09-18): Page.components.<form>.value = { <alan adı>: değer } — yalnız doğrulama sözlüğündeki alan bileşenleri girer (text/button gibi gösterim düğümleri girmez), boş alan null, aralık seçici { valueFrom, valueTo }; gönderim akışında self.value ile de okunur. DİKKAT (ölçüldü 2026-10-03): bu toplu değer boolean ve sayı alanlarında false/0'ı '' taşır — uca form.value/self.value ile VERME: nullable kolon NULL'lanır (kayıttaki 7/true silinir), NOT NULL kolonda 400 RowValidationFailed. Uca her alanı KENDİ prop'undan gönder (property ref ya da Page.components.<alan>.value); form resetForm ya da isClearFormInputsAfterSubmit kullanıyorsa sıfırlama alanın prop'unu da ''ya çevirir, o durumda değeri zorla: boolean <alan>.value === true, sayı typeof <alan>.value === 'number' ? <alan>.value : null. required da değeri aynı biçimde okur: 0 ve false BOŞ sayılır (bileşen notlarına bak). Page.components.<form>.isValid mount'ta ve her değişimde SESSİZCE yeniden hesaplanır (form düzeyi kurallar dahil) — mesaj göstermez. GİZLİLİK MUAFİYETİ YALNIZ ALANIN KENDİ isHidden'ıdır: isHidden'lı bir kabın (container/box) ya da appearance.hideOn ile gizlenen kabın içindeki zorunlu alan doğrulanmaya devam eder, ekranda olmadığı hâlde hata listesine girer ve gönderimi KİLİTLER; form düzeyi kural (validations element) hedefi kendisi gizli olsa bile koşar. Koşullu bölümde zorunlu alanların her birine kendi isHidden koşulunu ver, kabı gizlemeye güvenme. isHidden bir ifadeye ({{ }}) bağlıysa isValid bir adım geriden gelebilir (gönderim gizliliği canlı okur); gizli alanların değeri value'da kalır (ölçüldü 2026-10-03); submit düğmesini {{ !Page.components.<form>.isValid }} ile kilitlemenin yolu budur. İkisini setter ile YAZMA, sonraki değişimde ezilir.",
138
138
  "Düzen prop grubunun TAMAMINI taşır (2026-09-01): layout ('flex' | 'grid') + direction/horizontalAlign/verticalAlign/size/gap/wrap, varsayılanları container ile aynı (flex · row · left · top · shrink · medium · wrap açık). 'Form'a layout yazılmaz' eski bilgidir. layout prop'u hiç yazılmamış eski form kayıtları ızgara koordinatıyla (blocks[].position + size.w) dizilmeye devam eder. Platform kataloğu component_get'te hâlâ eski şemayı gösteriyorsa kayıt yenilenmemiştir (ARC-024).",
139
139
  "Alan doğrulaması komponentin kendi validations dizisindedir: [{ type: 'required'|'email'|'max-length'…, message, value, isActive }] — dördü de zorunlu, eski 'maxLength' yazımı kaydedilir ama runtime doğrulayıcı onu TANIMAZ ve sessizce hiçbir şey doğrulamaz. min-value/max-value tarih kuralının çıplak 'yyyy-MM-dd' değeri YEREL gün okunur (2026-09-18): sınır gününe eşit bir alan değeri her saat diliminde geçer (eskiden UTC okunup Türkiye'de 3 saat kayıyordu, CQ-252).",
140
140
  "message bir ÇEVİRİ ANAHTARI olabilir: 't:' önekiyle yazılan mesaj (ör. 't:form.required') uygulamanın çeviri sözlüğünden çözülür — hem alanın kendi hata metninde hem form-validation-errors listesinde. Öneksiz mesaj yazarın kendi cümlesidir ve iki nokta içerse bile aynen gösterilir. Çözüm form MOUNT olurken bir kez yapılır: ekranda hata dururken dil değiştirilirse metin sayfa yenilenene kadar eski dilde kalır.",
@@ -148,8 +148,8 @@ export const componentKnowledge = {
148
148
  validations: "FORM DÜZEYİ kurallar — alanın kendi validations dizisi DEĞİL: [{ name, element, condition, message }]. condition bir ifade ya da fx bağıdır ve TRUE olunca kural KIRILMIŞ sayılır ('table1.rows.length <= 0', 'Page.components.end.value < Page.components.start.value'); element (elementId) işaretlenir, message (t: anahtarı olabilir) listeye o alanın etiketiyle girer. İŞARET ve MESAJ yalnız gönderimde; koşulun kendisi isValid için her alan değişiminde sessizce yeniden değerlendirilir (formun dışındaki bir düğüme bağlı koşul — 'table1.rows.length' gibi — yalnız o anlarda ve gönderimde okunur). Repeater içindeki form için value/isValid tanımsızdır (satır parametresi taşımaz).",
149
149
  title: "Başlık bandının metni; boş bırakılırsa bant ve alt çizgisi hiç çizilmez.",
150
150
  isLoading: "Açıkken alanlar yerine yükleme animasyonu — çocuklar DOM'dan kalkar, kapanınca yeniden mount olur (mount olayları ikinci kez çalışır). Sorgu sonucu gelene kadar formu kapatmak için akıştan açılıp kapatılır.",
151
- value: "RUNTIME YAZAR, okunur: { <alan adı>: değer } — alan adı düğümün name'i, boş alan null, aralık seçici { valueFrom, valueTo }; gösterim düğümleri girmez. Panelde görünmez, setter ile yazılmaz.",
152
- isValid: "RUNTIME YAZAR, okunur: bütün alan kuralları ve form düzeyi kurallar şu an geçiyor mu. Mount'ta ve her değişimde sessizce güncellenir, mesaj göstermez; gizli alanlar muaf."
151
+ value: "RUNTIME YAZAR, okunur: { <alan adı>: değer } — alan adı düğümün name'i, boş alan null (boolean/sayı alanında false/0 da '' olur — uca verme, behavior'a bak), aralık seçici { valueFrom, valueTo }; gösterim düğümleri girmez. Panelde görünmez, setter ile yazılmaz.",
152
+ isValid: "RUNTIME YAZAR, okunur: bütün alan kuralları ve form düzeyi kurallar şu an geçiyor mu. Mount'ta ve her değişimde sessizce güncellenir, mesaj göstermez; yalnız KENDİ isHidden'ı açık alanlar muaf — gizli kabın içindeki alan muaf değil."
153
153
  }
154
154
  },
155
155
  box: {
@@ -448,7 +448,7 @@ export const componentKnowledge = {
448
448
  'Liste açıldığında seçili olanlar (ve seçili çocuğu olan başlıklar) başa alınır; sıra liste AÇILDIĞI ANDA dondurulur, seçim değiştikçe satırlar zıplamaz.',
449
449
  "optionType='grouped' iken children alt satırları çizer; çocuğu olan satır VARSAYILAN OLARAK SEÇİLEMEZ: kutusu çizilmez ve satırın kendisi disabled'dır, açıp kapatmak yalnız sağdaki ok düğmesiyle (ya da klavyede Enter/ok tuşlarıyla) olur — başlık da seçilebilsin isteniyorsa o option'a isSelectable ver. optionType='single' iken children/isExpanded/isSelectable okunmaz.",
450
450
  'Arama kutusu boşken Backspace son etiketi siler; etiketler tek satıra sığmazsa fazlası "+n" rozetinde toplanır (kilitli/viewmode alanda rozet tıklanınca kalanları listeler).',
451
- "Doğrulama kuralları: required · minItems · maxItems (2026-09-11; `pattern` bildirimi kaldırıldı — değeri dizi olan alanda uygulanacak metin yok ve panelde hiç çizilemiyordu). Panel bu iki sayı kuralını 'Min length' / 'Max length' adıyla çizer; dizide uzunluk = SEÇİLİ ELEMAN SAYISI."
451
+ "Doğrulama kuralları: required · minItems · maxItems (2026-09-11; `pattern` bildirimi kaldırıldı — değeri dizi olan alanda uygulanacak metin yok ve panelde hiç çizilemiyordu). Panel bu iki sayı kuralını 'Min length' / 'Max length' adıyla çizer; dizide uzunluk = SEÇİLİ ELEMAN SAYISI. minItems/maxItems yazılırsa gateway min-length/max-length'e çevirir."
452
452
  ],
453
453
  propNotes: {
454
454
  value: "Seçili değerlerin dizisi — ilişki alanlarında etiket değil id yazılır/okunur. Düz değer dizisi de (['a', 'b']) geçerlidir; etiketler options yüklendiğinde tazelenir.",
@@ -469,7 +469,7 @@ export const componentKnowledge = {
469
469
  "Seçenekler iki kaynaktan gelir: statik options prop'u yerel olarak süzülür; komponente search olayına akış bağlıysa süzme SUNUCUDADIR ve options akıştan geldiği gibi gösterilir (dropdown/autocomplete deseninin aynısı). Eklenmiş etiketler her iki modda da listeden düşer.",
470
470
  'Olaylar: mount (render katmanı atar) · change (etiket eklenince/silinince/rengi değişince, setter) · search · focus · blur. Etiketin rengini değiştirmek de change yayar — renk değerin parçasıdır.',
471
471
  'Yinelenen etiket eklenemez: ölçü kırpılmış + küçük harftir ("Ankara" varken "ankara" eklenmez). Yapıştırma , ve satır sonundan böler, her parça ayrı etiket olur; options ile eşleşen parça seçeneğin RENGİYLE eklenir.',
472
- "Zorunluluk ve eleman SAYISI sınırı Validations'tan kurulur: Required · Min length · Max length. Dizi alanında 'length' eleman sayısını ölçer (CQ-226 ad ikiliği). Bileşen bu sınırı kendisi GÖRMEZ, yani sınır dolunca giriş kapanmaz, kural gönderimde işler (CQ-093)."
472
+ "Zorunluluk ve eleman SAYISI sınırı Validations'tan kurulur: Required · Min length · Max length. Dizi alanında 'length' eleman sayısını ölçer (CQ-226 ad ikiliği); minItems/maxItems yazılırsa gateway min-length/max-length'e çevirir. Bileşen bu sınırı kendisi GÖRMEZ, yani sınır dolunca giriş kapanmaz, kural gönderimde işler (CQ-093)."
473
473
  ],
474
474
  propNotes: {
475
475
  value: 'Seçili etiketler: [{ value, color? }]. Renk hex (#rrggbb); verilmezse etiket gri çizilir.',
@@ -488,11 +488,11 @@ export const componentKnowledge = {
488
488
  behavior: [
489
489
  "change HER TUŞTA yayılır; değer kesinleşince (blur/Enter) changed yayılır — arama sorgusu ya da kayıt gibi pahalı akışı changed'e (ya da enter'a) bağla, change'e değil.",
490
490
  "enter yalnız Enter'da yayılır; textinput bir form içindeyse Enter aynı anda formun submit akışını da tetikler — kaydetmeyi forma bağla, enter'a bağlarsan iki kez çalışır.",
491
- "inputMask seçiliyse yazılan metin anında maskeye biçimlenir ve value biçimli halidir ('0(555) 123 45 67' gibi); ham rakam istiyorsan akışta ayıkla. Maske dışı karakterler sessizce düşer."
491
+ "inputMask seçiliyse KULLANICININ yazdığı metin anında maskeye biçimlenir ve value biçimli hâlidir ('0(555) 123 45 67'); kullanıcı yazarken maske dışı karakterler sessizce düşer. Akıştan/kayıttan yazılan değer ise OLDUĞU GİBİ kalır (ekranda biçimli görünür). Form, biçimlenince hiçbir karakter kaybetmeyen ham değeri ('5551234567') kabul eder ve akışa HAM gönderir; karakter kaybettiren değer ('+905551234567', fazla hane, küçük harf) ile tarih/saat/son kullanma maskelerindeki ayraçsız değer 'Value does not match the selected input mask.' ile formu kilitler — o maskelere akıştan biçimli yaz. Ham rakam istiyorsan akışta ayıkla (2026-10-03)."
492
492
  ],
493
493
  propNotes: {
494
494
  clearable: 'Değer varken alanın sağında × düğmesi çizer; tıklanınca value boşalır ve change + changed yayılır. Kilitli (disabled/readonly/viewmode/loading) alanda çizilmez.',
495
- inputMask: "Hazır maske anahtarı (pattern:/date:/time:/regex:…); serbest desen için 'custom:' öneki ('custom:(###) ###-####').",
495
+ inputMask: "Hazır maske anahtarı (pattern:/date:/time:/regex:…); serbest desen için 'custom:' öneki ('custom:(###) ###-####'). IBAN anahtarı 'iban:TR## #### #### #### #### #### #### ##' dizesi 28 # okunur ama deseni TR + 24 rakamdır (26 karakter); geçerli TR IBAN biçimlenir ve Form maske denetiminden geçer (2026-10-03).",
496
496
  prefixIcon: "Alanın İÇİNDE solda çizilen salt görsel ikon ({ name: 'search' }); value'ya karışmaz. Renk verilmezse ikincil metin rengi. Viewmode'da hiç çizilmez: alan düz metne döner ve ikon yuvası kadar girinti bırakmaz.",
497
497
  suffixIcon: "Alanın içinde sağda çizilen ikon; × ve spinner'ın sağında durur. Viewmode'da hiç çizilmez.",
498
498
  maxChars: "SERT sınır: fazlası YAZILAMAZ (maskesizde tarayıcı maxlength'i, maskelide biçimlenmiş değer kesilir). Akıştan gelen daha uzun değer aynen gösterilir, yeniden yazılmaz (sayaç 45/40 okur). validations.maxLength'ten FARKLIDIR: o yalnız doğrular. 0/boş = sınırsız.",
@@ -537,7 +537,9 @@ export const componentKnowledge = {
537
537
  "change HER TUŞTA ve artı/eksi düğmesinde yayılır; değer kesinleşince (blur/Enter, düğme tıklaması) changed yayılır — sorgu ya da kayıt gibi pahalı akışı changed'e (ya da enter'a) bağla.",
538
538
  "minValue/maxValue yazarken değil KESİNLEŞİNCE kırpar: min 10 alanına '15' yazılabilir (ilk tuşta 1 → 10'a çekilmez); blur/Enter'da aralık dışı değer sınıra çekilir ve change+changed yayılır. 0 gerçek sınırdır, boş = sınırsız.",
539
539
  "Boşaltılan alan 0 sayılır: silinince change 0 yayılır, blur'da alan 0 gösterir; null/boş değer durumu yoktur. placeholder yalnız value akıştan null/boş yazıldığında görünür.",
540
- "allowDemicalPoints kapalıyken tarayıcının native number alanıdır (tam sayı kullanımı; step tam sayı ver), açıkken locale'e göre biçimlenen metin alanıdır (tr 1.250,50 / en 1,250.50) — decimalPlaces, useThousandsSeparator ve locale yalnız açıkken okunur. Modu kapatıp açmak kesri düşürür."
540
+ "allowDemicalPoints kapalıyken tarayıcının native number alanıdır (tam sayı kullanımı; step tam sayı ver), açıkken locale'e göre biçimlenen metin alanıdır (tr 1.250,50 / en 1,250.50) — decimalPlaces, useThousandsSeparator ve locale yalnız açıkken okunur. Modu kapatıp açmak kesri düşürür.",
541
+ "required 0'ı da BOŞ sayar: kullanıcının sildiği kutu 0 yayar ve yine 'zorunlu' hatası verir. 0 meşruysa required koyma; 'pozitif olmalı' için min-value: 1 kullan. value'yu açıkça yaz: yazılmazsa ekranda 0 görünür ama form boş sayar ve kullanıcının o boş kutuya yazdığı 0 olay yaymaz (ölçüldü 2026-10-03).",
542
+ "Tam sayı kipinde (allowDemicalPoints kapalı) alan tarayıcının min/max/step denetimini taşır ve Form'da novalidate yoktur: açılışta ya da akıştan gelen aralık dışı ya da adım ızgarası dışı değer (değersiz düğümün gösterdiği 0 + minValue 1, akıştan 15 > maxValue 10, akıştan 2.5 + step 1) Kaydet'te TARAYICI balonuyla gönderimi durdurur — submit akışı koşmaz, hata listesi boş kalır, aynı formdaki diğer alanların hataları da görünmez. Kullanıcının yazdığı aralık dışı değer blur/Enter'da sınıra çekilir; adım dışı ondalık (2.5, step 1) çekilmez ve aynı balona takılır. Adım ızgarası minValue'dan başlar (min 1, step 10 → 1, 11, 21…); bileşenin + düğmesi 0'dan 10 üretip balona takılabilir. Bu yüzden value'yu aralıkta ve ızgarada yaz, minValue 0'dan büyükse value'yu mutlaka yaz. isReadonly alan bu denetimden muaftır. Ondalık kipte (allowDemicalPoints açık) min/max/step hiç uygulanmaz: akıştan gelen aralık dışı değer olduğu gibi gider (ölçüldü 2026-10-03)."
541
543
  ],
542
544
  propNotes: {
543
545
  prefix: "Girdinin içinde SOLDA çizilen salt görsel ek (₺, $ gibi); value'ya karışmaz. Viewmode'da da çizilir — değerin anlamını taşır.",
@@ -552,21 +554,23 @@ export const componentKnowledge = {
552
554
  incrementalinput: {
553
555
  summary: 'Otomatik kod alanı: önek + ayraç + sıfır dolgulu sayaç (INV-0001); sıradaki kodu runtime’da sunucudan çeker. Sayısal stepper DEĞİLDİR — artır/azalt için numberinput.',
554
556
  behavior: [
555
- 'Sunucu isteği YALNIZ runtime rotalarında atılır (/preview, /view, /dev, /branch) ve ilki 1 sn gecikmeyle gider; tasarım kanvasında hiç atılmaz. Kanvasta alan boş kalır, üretilecek kod YER TUTUCU olarak önizlenir (önek/ayraç/sayaç/hane böylece kanvasta görünür).',
557
+ "MCP ile kurarken separator, counter, digitcount, autoFetchNextCode:true ve field AÇIKÇA yazılır: component_get.defaults (separator '-', counter 1, digitcount 2, autoFetchNextCode true) yalnız Studio'dan bırakılan düğüme yazılır; düğümde yoksa bileşen ''/null/null/false okur, alan düz metin kutusuna döner, istek gitmez ve kayıt NULL gider (ölçüldü 2026-10-02).",
558
+ "Sunucu isteği YALNIZ runtime rotalarında atılır (/preview, /view, /dev, /branch) ve ilki 1 sn gecikmeyle gider; tasarım kanvasında hiç atılmaz. Değer boşken alan (kanvasta VE runtime'da) counter'dan YERELDE hesaplanan kodu gri yer tutucu olarak gösterir — bu, sunucunun vereceği numara da değer de DEĞİLDİR (ölçüldü 2026-10-02: yer tutucu NUL-0001, gerçek sıradaki NUL-0003).",
556
559
  'İstek POST entities/{entityId}/row/next-code — entity + alan (field) seçilmeden, sayaç ya da hane 0 iken, ya da önek varken ayraç boşken hiç gitmez. Önek BOŞ olabilir: o zaman ayraç da düşer ve kod yalnız sayaçtır.',
557
560
  'change olayı İKİ farklı yük taşır: sunucudan kod gelince değer NESNEdir ({ Prefix, Separator, Counter, DigitCount, FieldId }) — kodun metni değil kodu ÜRETEN şekil; kullanıcı elle yazınca değer düz METİNdir. self.value okuyan akış ikisini de karşılamalı.',
558
- 'Elle yazılan değer korunur: yazmaya başlandıktan sonra dönen sunucu yanıtı atılır, alan yeniden yazılmaz. Aynı entity/alan/şekil için istek bir kez atılır (şekil değişirse yenilenir).',
561
+ "Elle yazılan değer korunur: yazmaya başlandıktan sonra dönen sunucu yanıtı atılır, alan yeniden yazılmaz. Değer dolu METİNse istek hiç gitmez. İstek açılışta bir kez gider; runtime'da akış şekil prop'unu (prefix/counter…) değiştirirse yeni istek ÖNCEKİ şekille gider (ölçüldü 2026-10-02: prefix YEN yazıldı, kod NUL-0008 kaydedildi) — şekli akıştan değiştirme.",
562
+ "Form temizlenince (isClearFormInputsAfterSubmit ya da resetForm) değer null olur ve yeni kod GELMEZ: art arda kayıtta ikinci kayıt kodsuz gider (nullable alanda NULL, NOT NULL alanda 400 RowValidationFailed). Ardışık kayıt formunda temizlemeyi KAPALI tut — değer şekil nesnesi kalır ve backend her kayıtta yeni kod üretir (ölçüldü: WRK-0009, WRK-0010); ekranda eski kod kalır, yeni numarayı kayıttan dönen satırdan al.",
559
563
  'next-code isteği başarısız olursa alan invalid görünür ve hata metni invalidText’in YERİNE yazılır — panelde isInvalid kapalı olsa bile.',
560
564
  'Dört kip ekranda ayrı görünür: isDisabled gri + tıklanamaz, isReadonly okunur zemin, isViewmode çerçevesiz düz metin (önizleme de çizilmez), isLoading alanı dondurur ve sağda spinner döndürür. İstek sürerken bileşen isLoading görünümünü KENDİSİ açar.'
561
565
  ],
562
566
  propNotes: {
563
- value: 'Ya sunucudan gelen şekil nesnesi ya da elle yazılmış metin (davranışa bak). Kayıtlı satır nesneyi saklar, ekranda kodun metni görünür.',
564
- prefix: 'Kodun başındaki sabit ek (INV). Boş bırakılabilir; boşsa ayraç da yazılmaz.',
565
- separator: 'Önek ile sayaç arasındaki ayraç. Önek varsa ZORUNLUDUR — boşsa kod hiç üretilmez.',
566
- counter: 'Sayacın başlangıç değeri (en az 1). Sıradaki numarayı backend belirler; bu değer isteğin başlangıç noktasıdır.',
567
- digitcount: 'Sayacın kaç haneye sıfırla doldurulacağı (en az 1): 4 → 0001.',
567
+ value: "Ya sunucudan gelen şekil nesnesi ya da elle yazılmış metin (davranışa bak). Nesneyi uca OLDUĞU GİBİ gönder: backend kodu üretir ve alanı kod METNİYLE değiştirir (satırda INV-0003 gibi metin durur, nesne değil). Nesnedeki Counter başlangıç değeridir, kaydedilen numara değil; kod metni akışta YOKTUR — kayıttan dönen satırdan oku (Steps.<adım>.data.<alan>) (ölçüldü 2026-10-02).",
568
+ prefix: "Kodun başındaki sabit ek (INV). Boş bırakılabilir; boşsa ayraç da yazılmaz. Backend sınırı en çok 3 karakter: önizleme fazlasını gösterir ama kayıt 400 InvalidParameterException ile düşer, satır yazılmaz (ölçüldü 2026-10-02).",
569
+ separator: "Önek ile sayaç arasındaki ayraç. Önek varsa ZORUNLUDUR — boşsa kod hiç üretilmez. Backend sınırı 1 karakter: '--' önizlenir ama kayıt 400 InvalidParameterException ile düşer.",
570
+ counter: "Sayacın başlangıç değeri (en az 1). Sıradaki numarayı backend belirler; bu değer isteğin başlangıç noktasıdır. Numara alan başına TEK sıradır: önek değişse de kaldığı yerden devam eder (ölçüldü: INV-0002'den sonra NUL önekiyle ilk kod NUL-0003).",
571
+ digitcount: "Sayacın kaç haneye sıfırla doldurulacağı (1–12): 4 → 0001. 12'yi aşan değer önizlenir ama kayıt 400 InvalidParameterException ile düşer.",
568
572
  autoFetchNextCode: 'Kapalıyken istek atılmaz VE önek/ayraç/sayaç/hane hiç okunmaz — alan düz bir metin girdisine döner, kanvas önizlemesi de kaybolur.',
569
- field: 'Kodun hangi tablonun hangi alanı üzerinden ilerleyeceği (entity + alan). Seçilmeden otomatik çekme çalışmaz.',
573
+ field: 'Kodun hangi tablonun hangi alanı üzerinden ilerleyeceği. Biçim: JSON metni {"entityId":"<GUID>","fieldId":"<GUID>"} (Studio bunu yazar) ya da aynı anahtarlı nesne; ikisi de GUID. Slug çalışmaz: düz slug sessizce istek atmaz, JSON içinde slug 404 alır; { entity, field } anahtarları okunmaz. Alan Incremental tipinde ve formun YAZDIĞI alanla aynı olmalı — değilse önizleme çalışır ama kayıt 400 InvalidParameterException ile düşer (ölçüldü 2026-10-02).',
570
574
  placeholder: 'Alan boşken görünen metin. Otomatik çekme açıkken önizlenen kod bunu EZER; buradaki metin yalnız otomatik çekme kapalıyken görünür.',
571
575
  prefixIcon: "Alanın İÇİNDE solda çizilen salt görsel ikon ({ name: 'tag' }); value'ya karışmaz. Viewmode'da çizilmez.",
572
576
  suffixIcon: "Alanın içinde sağda çizilen ikon; yükleniyor spinner'ının sağında durur. Viewmode'da çizilmez.",
@@ -600,7 +604,8 @@ export const componentKnowledge = {
600
604
  'Kilitli (isDisabled/isReadonly/isViewmode) ya da isLoading olan kutu hiçbir olay yaymaz; focus/blur de susar.',
601
605
  'value akıştan METİN gelirse: "false" · "0" · "no" · "off" · "null" · "undefined" · "nan" · boş metin YANLIŞ sayılır, kalan dolu metin doğru. Yine de alana boolean yaz — sözleşme boolean’dır.',
602
606
  'Zorunluluk prop değil, Form’un doğrulama listesinden gelir (`required`). checkbox’ın TEK doğrulama kuralı odur — panel boolean tipinin listesini çizer, desen/uzunluk kuralı yoktur.',
603
- 'Birden çok seçenek gerekiyorsa checkbox yığmak yerine checkboxgroup kullan: değeri string[] ve seç-tümü/sayaç onda hazır.'
607
+ 'Birden çok seçenek gerekiyorsa checkbox yığmak yerine checkboxgroup kullan: değeri string[] ve seç-tümü/sayaç onda hazır.',
608
+ "required = kutu İŞARETLİ olmalı: işaretsiz (false) kutu boş sayılır ve 'zorunlu' hatası verir — onay/şart kabul kutusu için doğru kullanım. false meşru bir cevapsa required koyma (ölçüldü 2026-10-03)."
604
609
  ],
605
610
  propNotes: {
606
611
  labelPosition: "Etiketin kutuya göre yeri: 'right' (varsayılan, kutu solda) ya da 'left'. Kutu her iki yerleşimde de etikete yapışır; ayrı bir hizalama prop’u yoktur, satırın hizası düğümün appearance kanalından gelir.",
@@ -619,7 +624,8 @@ export const componentKnowledge = {
619
624
  'value akıştan METİN gelirse: "false" · "0" · "no" · "off" · "null" · "undefined" · "nan" · boş metin YANLIŞ sayılır, kalan dolu metin doğru (checkbox ile ortak normalizasyon). Yine de alana boolean yaz — sözleşme boolean’dır.',
620
625
  'Zorunluluk prop değil, Form’un doğrulama listesinden gelir (`required`). switch’in TEK doğrulama kuralı odur — panel boolean tipinin listesini çizer, desen/uzunluk kuralı yoktur.',
621
626
  'isIndeterminate YOKTUR ve bilerek eklenmedi: anahtar tanımı gereği ikili, "kısmen açık" diye bir durumu yok. Üçüncü durum gerekiyorsa checkbox kullan.',
622
- 'Anahtarın rengi TEMANIN vurgu renginden gelir; renk prop’u yoktur ve style düğümün KÖKÜNE yazılır (kolun kendisine ulaşmaz) — "yeşil anahtar" gibi bir istek tema ayarıdır, prop değil.'
627
+ 'Anahtarın rengi TEMANIN vurgu renginden gelir; renk prop’u yoktur ve style düğümün KÖKÜNE yazılır (kolun kendisine ulaşmaz) — "yeşil anahtar" gibi bir istek tema ayarıdır, prop değil.',
628
+ "required = anahtar AÇIK olmalı: kapalı (false) anahtar boş sayılır ve 'zorunlu' hatası verir. Kapalı da meşru bir seçimse required koyma (ölçüldü 2026-10-03)."
623
629
  ],
624
630
  propNotes: {
625
631
  labelPosition: "Etiketin anahtara göre yeri: 'right' (varsayılan, anahtar solda) ya da 'left'. Anahtar her iki yerleşimde de etikete yapışır; yükleniyor spinner'ı kolla etiketin ARASINDA durur — 'right'ta kolun sağında, 'left'te satırın sonunda.",
@@ -749,7 +755,7 @@ export const componentKnowledge = {
749
755
  fileUpload: {
750
756
  summary: 'Sürükle-bırak yükleme ALANI (dropzone): büyük bir hedef kutu, tıklayınca da dosya penceresi açar, seçilenleri silinebilir çip listesi olarak gösterir ve görselleri kırpabilir. Tek satırlık alan için fileInput, düz buton için filebutton. Seçim self.value’dan okunur.',
751
757
  behavior: [
752
- "Komponentin YAYDIĞI value her zaman DİZİDİR: [{ value: base64 data URL, fileName, type: 'file', ref: '' }]. isMultiple:false iken tek elemanlı dizi — nesne değil. Akış tek nesne ya da düz URL metni yazarsa da liste çizilir (2026-09-17'ye kadar hiç çizilmiyordu).",
758
+ "Komponentin change ile YAYDIĞI value her zaman DİZİDİR — isMultiple:false iken de tek elemanlı dizi, çip silinince []. Yeni seçilen dosya { type:'file', ref:'', fileName, value:<base64 data URL> } olur; akışın yazdığı kayıtlı dosyalar listede OLDUĞU GİBİ kalır. Akışın yazdığı değer change gelene kadar yazıldığı şekliyle durur (nesne yazdıysan value nesnedir). Kayıtlı dosyayı geri basmak için uç ya da workflow table get adımının döndüğü FILE değerini OLDUĞU GİBİ yaz — tekil alanda { type:'ref', value:<fileId>, fileName, absolute_path, … }, çoka-çok alanda bunun dizisi: her dosya ayrı çipte adıyla çizilir, dokunmadan kaydetmek dosyaları korur. Düz URL metni ya da dizisi YAZMA: çip aynı görünür ama kayıt bozulur — çoka-çok alanda 500 JsonException, tekil alanda dosya sessizce silinir, bir çip silinince kalan URL { type:'file', value:<URL> } olarak yayılır ve 500 FormatException verir (ölçüldü 2026-10-02).",
753
759
  'Çipe tıklamak o dosyayı listeden siler ve change olayını KALAN listeyle yayar; ayrı bir silme olayı yoktur. Kilitliyken (isDisabled/isReadonly/isViewmode) ne ekleme ne silme olur, sürükleme de tepki vermez.',
754
760
  'allowedFiletypes KAPALI bir uzantı listesidir: seçilmeyen uzantı dosya penceresinde de görünmez, sürüklenirse reddedilir ve komponentin içinde tür uyarısı çizilir. jpg ve jpeg AYRI satırdır — ikisini de kabul edeceksen ikisini de seç. Listede HİÇ olmayan bir uzantı için customFiletypes metin alanına yaz ("heic, p7m" — virgül/boşluk/noktalı virgül ayırır, baştaki . ve * atılır, küçük harfe iner); iki kaynak birleşir. İkisi de boşsa her tür serbesttir.',
755
761
  'maxFileSizeMb / maxFileCount yalnız kullanıcıya hızlı geri bildirimdir, sunucu tarafını bağlamaz. 0, negatif ya da boş değer SINIRSIZ demektir. Sınıra takılan dosya listeye girmez, geçenler girer (hepsi ya da hiçbiri değil); boyut ihlali adet ihlalinin önüne geçer. Adet sınırı çok seçimde zaten seçili dosyaları da sayar.',
@@ -758,7 +764,7 @@ export const componentKnowledge = {
758
764
  'Doğrulama: required ve pattern. isInvalid/invalidText Form’un merkezi hatasıdır; komponentin kendi tür ve sınır uyarıları ondan ayrı çizilir.'
759
765
  ],
760
766
  propNotes: {
761
- value: "Panelde GİZLİ (.hide()): değeri elle yazmak yerine akıştan bağla ya da change'den sonra self.value ile oku. Akış dizi yerine tek nesne ya da düz URL metni yazarsa da çip çizilir (metinde ad son yol parçasından alınır).",
767
+ value: "Panelde GİZLİ (.hide()): akıştan bağla, change'den sonra self.value ile oku. Kayıtlı dosyayı forma basarken uç/workflow get adımının FILE değerini (type:'ref') OLDUĞU GİBİ yaz, düz URL yazma. TEKİL FILE alanına gönderirken diziyi açmak ZORUNLU — değer v ise Array.isArray(v) ? (v[0] || { type:'ref', value:null }) : v. Dizi gönderilirse tablo ucu 200 döner ama alanı sessizce NULL'lar: yeni dosya açılmaz, mevcut dosya da silinir. Yalnız v[0] da yetmez: dokunulmamış formda değer akışın yazdığı nesnedir. Çoka-çok alana değer olduğu gibi gider: X ile silinen dosya düşer, kalan type:'ref' kayıtları korunur, yeni dosyalar açılır (ölçüldü 2026-10-02). Tasarımda value'ya drive dosyası BAĞLAMA ({ type:'file', ref:<fileId> } — Studio panelinin dosya seçicisi de bunu yazar): çalışma anı değeri { type:'file', ref, data:<URL> } olur, çip dosya adı yerine URL yazar ve kullanıcı dokunmadan kaydederse backend type:'file' kaydında yalnız value'yu okuduğu için tekil alan sessizce NULL'lanır (200 döner, mevcut dosya silinir; NOT NULL alanda 400 InvalidParameterException). Varsayılan dosya gerekiyorsa value'yu static ref nesnesi olarak ver: { type:'static', data:{ type:'ref', value:'<fileId>', fileName, absolute_path } } — çip adıyla çizilir, dokunmadan kayıt dosyayı yazar (ölçüldü 2026-10-02).",
762
768
  customFiletypes: "allowedFiletypes listesinde HİÇ olmayan uzantılar için serbest metin: 'heic, p7m' (virgül/boşluk/noktalı virgül ayırır). İki kaynak birleşir; fileInput'taki aynı adlı prop'la aynı sözleşme.",
763
769
  size: "sm | md | lg — metin ve ikon ölçeğini değiştirir; boş bırakılırsa uygulamanın form boyutu geçerlidir (2026-09-17 öncesinde hiçbir etkisi yoktu).",
764
770
  isMultiple: 'Kapalıyken yeni seçim eskisinin YERİNE geçer, listeye eklenmez.',
@@ -776,7 +782,7 @@ export const componentKnowledge = {
776
782
  'isLoading prop’u YOKTUR ve bilinçlidir (CQ-215): bu dosya ailesinde yükleniyor sözcüğü alan kilidi mi yükleme ilerlemesi mi, karar verilmedi. Alanı dondurmak için isDisabled kullan.'
777
783
  ],
778
784
  propNotes: {
779
- value: 'Tek dosyada { type, fileName, value } NESNESİ, isMultiple açıkken bu nesnelerin DİZİSİ; value base64 data URL’dir. Akıştan yazarken de aynı şekli yaz — okunamayan değer alanı BOŞ gösterir, hata vermez.',
785
+ value: "Tek dosyada NESNE, isMultiple açıkken nesnelerin DİZİSİ. Kullanıcının seçtiği YENİ dosya { type:'file', fileName, value:<base64 data URL> } olarak gelir ve uca böyle gider; çoklu kipte yeni seçim dizinin sonuna eklenir ve kayıtta yalnız o dosya açılır. Kayıtlı dosyayı forma geri basarken uç ya da workflow table get adımının döndüğü FILE değerini OLDUĞU GİBİ yaz — tekil alanda nesne ({ type:'ref', value:<fileId>, fileName, absolute_path, … }), çoka-çok alanda bu nesnelerin dizisi: kullanıcı dokunmadan kaydederse aynı değer uca gider, dosyalar referans olarak korunur, yeni dosya açılmaz; X ile silinen dosya gövdeden düşer. Tek dosyada çip adıyla çizilir; 2+ dosyada çip 'N dosya seçildi' yazar, adlar çipe tıklayınca açılan listededir. Kayıtlı dosyayı type:'file' ile (value'da URL ya da guid) YAZMA: ekranda fark görünmez ama backend value'yu base64 diye çözer ve kayıt 500 FormatException ile düşer. Asgari { type:'ref', value:<fileId> } kaydı geçirir ama tek dosya çipi 'undefined' yazar; fileName ve absolute_path da taşınmalı. row_get/row_list şekillerini uca geri yazma: tekil alanda düz guid ya da { id, display_name } tablo kaynaklı uçta alanı SESSİZCE NULL'lar (200; workflow table adımı bu ikisini doğru bağlar); çoka-çok alanda row_get/row_list'in [{ id, display_name }] dizisi uçta DA workflow table adımında DA bütün dosyaları sessizce siler, düz guid dizisi 500 JsonException verir. Boşaltmak için tekil alanda { type:'ref', value:null }, çoklu alanda [{ type:'ref', value:null }] ya da []. (ölçüldü 2026-10-02) Tasarımda value'ya drive dosyası BAĞLAMA ({ type:'file', ref:<fileId> } — Studio panelinin dosya seçicisi de bunu yazar): çalışma anı değeri { type:'file', ref, data:<URL> } olur, çip dosya adı yerine URL yazar ve kullanıcı dokunmadan kaydederse backend type:'file' kaydında yalnız value'yu okuduğu için tekil alan sessizce NULL'lanır (200 döner, mevcut dosya silinir; NOT NULL alanda 400 InvalidParameterException). Varsayılan dosya gerekiyorsa value'yu static ref nesnesi olarak ver: { type:'static', data:{ type:'ref', value:'<fileId>', fileName, absolute_path } } — çip adıyla çizilir, dokunmadan kayıt dosyayı yazar (ölçüldü 2026-10-02).",
780
786
  button_text: 'Gözat butonunun metni (varsayılan Browse); boşaltılırsa buton HİÇ çizilmez, görüntüleme ve salt okunur kiplerde de çizilmez. Adın snake_case olması kayıtlı sayfalar yüzündendir (ARC-085); buttonText yazmak unknown-prop verir.',
781
787
  placeholder: 'Dosya seçilmemişken gösterilen metin; boş bırakılırsa uygulama dilinde hazır metin yazar (Dosya seçiniz / Choose a file).',
782
788
  allowedFiletypes: 'Sabit listeden seçilen uzantılar (pdf, docx, doc, xlsx, xls, pptx, ppt, txt, csv, jpg, png, heic, zip…). Boş bırakmak her tür serbest demektir; listede olmayan uzantı için customFiletypes. Liste 2026-09-17’den beri bir ipucu değil gerçek bir ENGEL olduğu için her uzantı AYRI satırdır: jpg seçip jpeg seçmemek .jpeg dosyasını reddettirir, doc/docx ve xls/xlsx çiftleri de ayrıdır.',
@@ -795,7 +801,7 @@ export const componentKnowledge = {
795
801
  "Olaylar: mount ve change. mount'ı platform her düğüm için yayar ama bileşen bildirmediği sürece akışa bağlanamıyordu (2026-09-17'de bildirime eklendi)."
796
802
  ],
797
803
  propNotes: {
798
- value: "Seçili dosya(lar). Panelde ÇİZİLİR ve akıştan yazılabilir (2026-09-17'ye kadar gizliydi, bu yüzden AI sözlüğünde de görünmüyordu): kayıtlı bir dosyayı forma geri basmanın yolu budur. Tekli kipte nesne, çoklu kipte dizi; boşaltmak için null (çoklu kipte []). Kipiyle uyuşmayan şekil de OKUNUR: çoklu kipte yazılan tek nesne listelenir, tekli kipte yazılan dizinin ilk dosyası çizilir (2026-09-17'ye kadar ikisi de sessizce hiçbir şey göstermiyordu). İçeriği olmayan kayıt (ne value ne data ne absolute_path) hiç çizilmez.",
804
+ value: "Seçili dosya(lar). Panelde ÇİZİLİR ve akıştan yazılabilir (2026-09-17'ye kadar gizliydi, bu yüzden AI sözlüğünde de görünmüyordu): kayıtlı bir dosyayı forma geri basmanın yolu budur. Tekli kipte nesne, çoklu kipte dizi; boşaltmak için null (çoklu kipte []). Kipiyle uyuşmayan şekil de OKUNUR: çoklu kipte yazılan tek nesne listelenir, tekli kipte yazılan dizinin ilk dosyası çizilir (2026-09-17'ye kadar ikisi de sessizce hiçbir şey göstermiyordu). İçeriği olmayan kayıt (ne value ne data ne absolute_path) hiç çizilmez. Tasarımda value'ya drive dosyası BAĞLAMA ({ type:'file', ref:<fileId> } — Studio panelinin dosya seçicisi de bunu yazar): çalışma anı değeri { type:'file', ref, data:<URL> } olur, çip dosya adı yerine URL yazar ve kullanıcı dokunmadan kaydederse backend type:'file' kaydında yalnız value'yu okuduğu için tekil alan sessizce NULL'lanır (200 döner, mevcut dosya silinir; NOT NULL alanda 400 InvalidParameterException). Varsayılan dosya gerekiyorsa value'yu static ref nesnesi olarak ver: { type:'static', data:{ type:'ref', value:'<fileId>', fileName, absolute_path } } — çip adıyla çizilir, dokunmadan kayıt dosyayı yazar (ölçüldü 2026-10-02).",
799
805
  text: 'Butonun üzerindeki metin (varsayılan "Attach File"); ikon sabittir. Alanın başlığı ayrı bir prop\'tur: label.',
800
806
  description: 'Butonun altındaki yardım metni. Dosya seçilir seçilmez yerini dosya listesi alır, seçim temizlenince geri gelir (fileInput ile aynı kural).',
801
807
  allowedFiletypes: 'İzin verilen uzantılar (çoklu seçim, kapalı liste). Hem dosya diyaloğunun accept süzgecini hem de seçimden SONRAKİ denetimi besler: liste dışı bir dosya seçilirse değere girmez ve komponentin içinde "desteklenmiyor" uyarısı çizilir (2026-09-17; öncesinde yalnız diyalog süzgeciydi, "Tüm dosyalar" ile aşılabiliyordu). Hem allowedFiletypes hem customFiletypes boşsa her tür serbesttir.',
@@ -1705,7 +1711,7 @@ export const componentKnowledge = {
1705
1711
  checkboxgroup: {
1706
1712
  summary: 'Değeri DİZİ olan çoklu kutu grubu (multi-select’in liste hâlinde açılmış hâli).',
1707
1713
  behavior: [
1708
- 'Zorunluluk/en az-en çok seçim kuralları formun validations’ından gelir (required · minItems · maxItems).',
1714
+ 'Zorunluluk/en az-en çok seçim kuralları formun validations’ından gelir: required · min-length · max-length. Kayıt defteri (component_get.validations) iki sayı kuralını minItems/maxItems adıyla gösterir; gateway bunları min-length/max-length’e çevirir (backend ve runtime yalnız tireli adı tanır, Studio paneli de bu adla yazar). Form dizide uzunluk olarak SEÇİLİ ELEMAN SAYISINI ölçer.',
1709
1715
  "focus/blur GRUP düzeyindedir: kutular arasında Tab'lamak ayrı olay üretmez, yalnız grubun dışına çıkılınca/girilince bir kez yayılır (otpinput deseniyle aynı).",
1710
1716
  'DEĞER MODELİ düz bir string[]’dir, seçenek→boolean haritası DEĞİL: bir seçenek, değeri listede olduğu için işaretli görünür. Bu yüzden options içindeki value’lar BENZERSİZ olmalıdır — liste value ile anahtarlandığı için aynı value’yu iki seçeneğe vermek desteklenmez (geliştirme derlemesinde “duplicate keys” hatası fırlatır, üretimde liste bozulur).',
1711
1717
  'Seçenek listesinin TANIMADIĞI değerler korunur: tek tık da seç-tümü de listede karşılığı olmayan bir değeri silmez (sorguyla filtrelenmiş listede önceki seçimler kaybolmasın diye).',
@@ -1909,7 +1915,8 @@ export const componentKnowledge = {
1909
1915
  summary: 'Yıldız (ya da seçilen ikon) ile puanlama girişi.',
1910
1916
  behavior: [
1911
1917
  'Mevcut değere yeniden tıklamak puanı 0’a düşürür; allowHalf açıkken yıldızın sol yarısı yarım puan verir.',
1912
- "focus/blur yıldız GRUBUNUN tamamı için bir kez yayılır (kapsayıcıda focusin/focusout dinlenir), yıldız başına değil: yıldıza TIKLAMAK da alana girmek sayılır ve focus yayar, yıldızlar arasında gezinmek yeniden yaymaz, blur yalnız odak şeritten büsbütün çıkınca gelir. Kilitli (isDisabled/isReadonly/isViewmode) ya da yükleniyor alan hiçbir odak olayı yaymaz."
1918
+ "focus/blur yıldız GRUBUNUN tamamı için bir kez yayılır (kapsayıcıda focusin/focusout dinlenir), yıldız başına değil: yıldıza TIKLAMAK da alana girmek sayılır ve focus yayar, yıldızlar arasında gezinmek yeniden yaymaz, blur yalnız odak şeritten büsbütün çıkınca gelir. Kilitli (isDisabled/isReadonly/isViewmode) ya da yükleniyor alan hiçbir odak olayı yaymaz.",
1919
+ "required = en az bir yıldız: 0 puansız sayılır ve 'zorunlu' hatası verir (aynı yıldıza yeniden tık 0'a çeker) (ölçüldü 2026-10-03)."
1913
1920
  ],
1914
1921
  propNotes: {
1915
1922
  value: '0..max arası sayı; akıştan gelen değer bu aralığa kırpılır.',
@@ -1945,7 +1952,8 @@ export const componentKnowledge = {
1945
1952
  behavior: [
1946
1953
  'change sürüklerken sürekli, changed yalnız bırakıldığında yayılır — pahalı akışı changed’e bağla.',
1947
1954
  "focus/blur GRUP düzeyindedir (checkboxgroup/otpinput deseni): input'un kendisi değil dıştaki kapsayıcı dinler, tek giriş/çıkışta bir kez yayılır.",
1948
- 'isReadonly/isViewmode/isLoading rayı değiştirilemez yapar ama alanı ODAKLANABİLİR bırakır (değer klavyeyle ve ekran okuyucuyla okunur) ve kilitli alan focus/blur/change YAYMAZ; native disabled ve “yasak” imleci yalnız isDisabled’dan gelir.'
1955
+ 'isReadonly/isViewmode/isLoading rayı değiştirilemez yapar ama alanı ODAKLANABİLİR bırakır (değer klavyeyle ve ekran okuyucuyla okunur) ve kilitli alan focus/blur/change YAYMAZ; native disabled ve “yasak” imleci yalnız isDisabled’dan gelir.',
1956
+ "required 0'ı boş sayar: 0'a çekilmiş kaydırıcı 'zorunlu' hatası verir. 0 meşruysa required koyma; alt sınırı minValue ile ver (ölçüldü 2026-10-03)."
1949
1957
  ],
1950
1958
  propNotes: {
1951
1959
  value: 'Sayı; akıştan gelen değer [minValue, maxValue] aralığına kırpılır (0 geçerli sınırdır) ve adıma oturmuyorsa en yakın geçerli adımda GÖSTERİLİR (tutamağın gittiği yer) — akışa yazılan sayı bundan etkilenmez, ekrandaki ile aynı olması isteniyorsa adıma oturan bir değer yaz.',
@@ -1981,7 +1989,8 @@ export const componentKnowledge = {
1981
1989
  'value akıştan METİN gelirse: "false" · "0" · "no" · "off" · "null" · "undefined" · "nan" · boş metin YANLIŞ sayılır, kalan dolu metin doğru (checkbox/switch ile ortak normalizasyon, 2026-09-11). Yine de alana boolean yaz — sözleşme boolean’dır.',
1982
1990
  'Zorunluluk prop değil, Form’un doğrulama listesinden gelir (`required`) — switcher’ın TEK doğrulama kuralı odur. 2026-09-11’e kadar panel bu bileşen için hiç liste çizmiyordu (slug `ComponentValidationTypes` sözlüğünde yoktu), artık boolean tipinin listesi çiziliyor.',
1983
1991
  'Form içindeki switcher artık boolean bir alan olarak davranır: submit’te değeri boolean şemayla doğrulanır (önce metin tipine düşüyordu, yani kural yokken bile tip hatası veriyordu) ve formun Reset’i onu da başlangıç değerine döndürür (önce sıfırlamadan muaftı). 2026-09-11.',
1984
- 'Vurgulu segmentin rengi TEMANIN vurgu renginden gelir; renk prop’u yoktur ve style düğümün KÖKÜNE yazılır (segmentlere ulaşmaz).'
1992
+ 'Vurgulu segmentin rengi TEMANIN vurgu renginden gelir; renk prop’u yoktur ve style düğümün KÖKÜNE yazılır (segmentlere ulaşmaz).',
1993
+ "required false segmentini REDDEDER: false boş sayılır ve 'zorunlu' hatası verir. İki seçenek de meşruysa required KOYMA — switcher'ın değeri her zaman vardır (ölçüldü 2026-10-03)."
1985
1994
  ],
1986
1995
  propNotes: {
1987
1996
  label: 'Alanın adı, track’in üstünde. Boş bırakılırsa hiç çizilmez ve kontrolün ekran okuyucudaki adı iki segmentin metninden okunur — form içindeki switcher’a etiket yaz.',
@@ -158,7 +158,7 @@ export const componentEventContracts = {
158
158
  payload: {
159
159
  value: "string (elle yazılan metin) YA DA { Prefix, Separator, Counter, DigitCount, FieldId } (sunucudan kod gelince: kodun METNİ DEĞİL, kodu üreten şekil)"
160
160
  },
161
- reads: 'setter: self.value da yazılır. Elle yazarken HER TUŞTA, otomatik kod gelince bir kez yayılır — pahalı akışı changed’e bağla'
161
+ reads: 'setter: self.value da yazılır. Elle yazarken HER TUŞTA, otomatik kod gelince bir kez yayılır — pahalı akışı changed’e bağla. Yükte kodun METNİ yoktur; Counter başlangıç değeridir, kaydedilen numara değil — numarayı kayıttan dönen satırdan oku.'
162
162
  },
163
163
  changed: valueChange('string (alan bırakılınca kesinleşen metin; yalnız ELLE düzenlemede yayılır)'),
164
164
  focus: NO_DATA,
@@ -249,8 +249,8 @@ export const componentEventContracts = {
249
249
  },
250
250
  fileUpload: {
251
251
  change: {
252
- payload: { value: "Array<{ value: base64 data URL, fileName: string, type: 'file', ref: '' }>" },
253
- reads: 'self.value — dosya EKLENİNCE de SİLİNİNCE de (çipe tıklama) aynı olay yayılır ve yük her seferinde KALAN TÜM listedir, yalnız yeni dosyalar değil. isMultiple:false iken dizi tek elemanlıdır. Kilitliyken (isDisabled/isReadonly/isViewmode) hiç yayılmaz; tür/boyut/adet sınırına takılan dosya listeye girmez ve uyarı komponentin içinde çizilir, ama kabul KISMİDİR: geçen dosya varsa olay GEÇENLERLE yayılır, yalnız hepsi reddedilirse hiç yayılmaz. enableCrop açıkken görsel dosyalar kırpma katmanından geçtikten SONRA yayılır (İptal dosyayı düşürür; hepsi düşerse olay çalışmaz).'
252
+ payload: { value: "Array — her eleman: yeni seçimde { type:'file', ref:'', fileName, value:<base64 data URL> }; akışın yazdığı kayıtlı dosya AYNEN ({ type:'ref', value:<fileId>, fileName, absolute_path, … }); akış düz URL yazdıysa { type:'file', ref:'', fileName, value:<URL> } (kayıtta 500 FormatException)" },
253
+ reads: "self.value — dosya EKLENİNCE de SİLİNİNCE de (çipe tıklama) aynı olay yayılır ve yük her seferinde KALAN TÜM listedir, yalnız yeni dosyalar değil. isMultiple:false iken dizi tek elemanlıdır. Kilitliyken (isDisabled/isReadonly/isViewmode) hiç yayılmaz; tür/boyut/adet sınırına takılan dosya listeye girmez ve uyarı komponentin içinde çizilir, ama kabul KISMİDİR: geçen dosya varsa olay GEÇENLERLE yayılır, yalnız hepsi reddedilirse hiç yayılmaz. enableCrop açıkken görsel dosyalar kırpma katmanından geçtikten SONRA yayılır (İptal dosyayı düşürür; hepsi düşerse olay çalışmaz). Akışın yazdığı type:'ref' kayıtları yükte aynen kalır. isMultiple:false iken de dizidir (silmede []); tekil FILE alanına göndermeden önce ilk elemanı al (componentCatalog fileUpload.propNotes.value)."
254
254
  }
255
255
  },
256
256
  fileInput: {
@@ -456,7 +456,13 @@ const VALIDATION_TYPE_ALIASES = new Map([
456
456
  ...VALIDATION_CANONICAL_TYPES.map(type => [type.replace(/-/g, ''), type]),
457
457
  ['pattern', 'regex'],
458
458
  ['min', 'min-value'],
459
- ['max', 'max-value']
459
+ ['max', 'max-value'],
460
+ // OR-2 (2026-10-03): kayıt defteri checkboxgroup/multi-select/tag-input kuralını minItems/maxItems
461
+ // adıyla ilan eder (component_get.validations); backend ValidationType'ta bu ad yok, runtime da tanımaz.
462
+ // Studio paneli bu üçüne 'custom' şablonundan min-length/max-length yazar (ng-apps-types
463
+ // validation/constants.ts) ve Form uzunluk kuralı dizide SEÇİLİ ELEMAN sayısını ölçer.
464
+ ['minitems', 'min-length'],
465
+ ['maxitems', 'max-length']
460
466
  ]);
461
467
  const VALIDATION_VALUE_REQUIRED = new Set(['regex', 'max-length', 'min-length', 'max-value', 'min-value', 'value-options']);
462
468
  function canonicalValidationType(value) {
@@ -650,6 +656,34 @@ export function styleKeyNote(fixed) {
650
656
  + 'değer kaydedilir ama hiç uygulanmaz ve tek işareti tarayıcı konsoludur (F-06). '
651
657
  + "Seçici anahtarlara ('&__body', ':hover', '--token') dokunulmadı."];
652
658
  }
659
+ /**
660
+ * Y-15 — adı eksik, null ya da boş/boşluk olan düğüm runtime'ın ifade ön sözünü bozar: `getNodeNames`
661
+ * null adı `"null"` anahtarına çevirir, `runCode` `const null = Components["null"]` satırını üretir
662
+ * (`ng-apps-flow/src/flow-execution.functions.ts`) ve sayfadaki BÜTÜN ifadeler SyntaxError'la düşer;
663
+ * hata yalnız akış adımının runtimeErrors'ında görünür (ölçüldü 2026-10-03, `y15-adsiz`). Backend adı
664
+ * hiç denetlemez (`PageSchemaValidator.cs`); Studio her düğüme `name = elementId` yazar
665
+ * (`ScreenBuilderDroppable.svelte`). Kural deterministik olduğu için reddetmek yerine dolduruyoruz.
666
+ * Doldurulan ad da PAGE-009'dan geçmek zorunda: çağıran bunu denetimden ÖNCE koşar. Dolu ada dokunulmaz.
667
+ */
668
+ export function fillComponentNames(nodes) {
669
+ const filled = [];
670
+ for (const node of asObjects(nodes)) {
671
+ const name = node.name;
672
+ const blank = name === undefined || name === null || (typeof name === 'string' && name.trim().length === 0);
673
+ if (blank && typeof node.elementId === 'string' && node.elementId.length > 0) {
674
+ node.name = node.elementId;
675
+ filled.push(node.elementId);
676
+ }
677
+ filled.push(...fillComponentNames(node.children));
678
+ }
679
+ return filled;
680
+ }
681
+ export function componentNameNote(filled) {
682
+ if (filled.length === 0)
683
+ return [];
684
+ const access = filled.length === 1 ? filled[0] : "her düğümün kendi elementId'si";
685
+ return [`${filled.map(id => `'${id}'`).join(', ')}: name eksikti, elementId'den yazıldı — ifadelerde ${access} ile erişilir (adsız düğüm runtime'da sayfadaki bütün ifadeleri düşürür, Y-15)`];
686
+ }
653
687
  export function normalizeComponentDefaults(nodes) {
654
688
  const fixed = [];
655
689
  for (const node of asObjects(nodes)) {
@@ -708,6 +742,65 @@ export function normalizeComponentDefaults(nodes) {
708
742
  }
709
743
  return fixed;
710
744
  }
745
+ export const COMPONENT_BODY_RULE = "Asgari gövde: { elementId, name (= elementId), type, props, blocks: [{ breakpoint: 1024, size: { w, h }, position: { x, y } }] }. "
746
+ + "Gateway blocks'u doldurmaz: blocks'u olmayan ya da null olan düğüm platformda kayıt sırasında 500 NullReferenceException verir. "
747
+ + "Boyutu component_get çıktısındaki sizes'tan al; flex kapta position yalnız sırayı belirtir (sırayı index ile de verebilirsin, blocks yine gövdede olur), "
748
+ + 'ızgara kapta x:0, y: kardeşlerin altı (en büyük y+h).';
749
+ /**
750
+ * Y-13 / PAGE-037 — `blocks` alanı hiç olmayan ya da `null` olan düğümleri (çocuklar dahil) toplar.
751
+ * `DesignComponentManagementSchema.Blocks`'un varsayılanı yoktur; `ToDomain` null listeyi
752
+ * `AddBlocks(null)` ile atlar, `_blocks` başlatılmaz ve `Blocks` getter'ı kayıtta patlar
753
+ * (`DesignComponent.cs:36-37,305`; yeni düğümde revizyon yakalama `ToObject:464`, var olan düğümde
754
+ * `DesignComponentRepository.UpsertRange` → `Override`). Ölçüldü 2026-10-04: son 7 günde eklenen
755
+ * düğümlerin ~%20'si blocks'suz geldi ve hepsi 500 aldı. Konum ebeveynin düzenine bağlı olduğu için
756
+ * doldurmuyoruz, reddediyoruz. `blocks: []` platformda kaydolur (ölçüldü 2026-10-04, `y13-blocks`) ve buraya girmez.
757
+ */
758
+ export function missingBlocksNodes(nodes) {
759
+ const missing = [];
760
+ for (const node of asObjects(nodes)) {
761
+ if (node.blocks === undefined || node.blocks === null) {
762
+ missing.push(`'${String(node.elementId ?? node.name ?? node.type ?? '?')}': blocks eksik — şunu ver: [{breakpoint:1024, size:{w,h}, position:{x,y}}]`);
763
+ }
764
+ missing.push(...missingBlocksNodes(node.children));
765
+ }
766
+ return missing;
767
+ }
768
+ /**
769
+ * PAGE-037 kimlik ayağı — platform elementId'siz add_component'i 'missing-id' ile reddeder
770
+ * (`PageSchemaMicroOpApplier.cs` add_component dalı) ama mesajı kimliğin hangi alan olduğunu
771
+ * söylemez. Ajanlar `name` verip elementId'yi atladı (Y-13 neden analizi N-B3, 5 düğüm).
772
+ */
773
+ export function addComponentIdErrors(ops) {
774
+ const errors = [];
775
+ for (const op of asObjects(ops)) {
776
+ if (op.op !== 'add_component')
777
+ continue;
778
+ const component = op.component && typeof op.component === 'object' ? op.component : {};
779
+ const elementId = component.elementId;
780
+ if (typeof elementId === 'string' && elementId.trim() !== '')
781
+ continue;
782
+ const name = typeof component.name === 'string' && component.name.trim() !== '' ? component.name : undefined;
783
+ errors.push(`add_component: component.elementId yok (platform 'missing-id' ile reddeder) — kimlik elementId'dir; name ifade adıdır, ikisini aynı yaz${name ? ` (elementId: '${name}', name: '${name}')` : ''}`);
784
+ }
785
+ return errors;
786
+ }
787
+ /**
788
+ * PAGE-037. Yalnız çağıranın verdiği ağaca bakar: page_create'in eklediği page_container
789
+ * blocks'la üretilir, okunan kayıttaki düğümler (layout_patch) platformdan blocks listesiyle gelir.
790
+ * Sayfa/layout yazma kapısı (`assertScreenWriteGuards`), `page_schema_validate` ve
791
+ * `component_update` çağırır; `page_schema_patch` aynı denetimi op başına toplar.
792
+ */
793
+ export function assertComponentBlocks(nodes) {
794
+ const missing = missingBlocksNodes(nodes);
795
+ if (missing.length === 0)
796
+ return;
797
+ throw new PlatformApiError(`PAGE-037 — komponent gövdesinde blocks yok (istek gönderilmedi): ${missing.join(' · ')}. ${COMPONENT_BODY_RULE}`, 400);
798
+ }
799
+ export function assertAddComponentIds(ops) {
800
+ const errors = addComponentIdErrors(ops);
801
+ if (errors.length > 0)
802
+ throw new PlatformApiError(`PAGE-037 — ${errors.join(' · ')} (istek gönderilmedi). ${COMPONENT_BODY_RULE}`, 400);
803
+ }
711
804
  /**
712
805
  * add_component micro-op'unun taşıdığı komponent ağacı da aynı ToDomain yolundan geçer.
713
806
  */
@@ -804,6 +897,7 @@ export function normalizeSchemaOps(ops) {
804
897
  const components = [];
805
898
  const props = [];
806
899
  const validations = [];
900
+ const names = [];
807
901
  for (const op of asObjects(ops)) {
808
902
  if (op.op === 'replace_prop') {
809
903
  if (isStylePropName(String(op.prop ?? ''))) {
@@ -825,9 +919,10 @@ export function normalizeSchemaOps(ops) {
825
919
  }
826
920
  if (op.op !== 'add_component' || !op.component)
827
921
  continue;
922
+ names.push(...fillComponentNames([op.component]));
828
923
  components.push(...normalizeComponentDefaults([op.component]));
829
924
  }
830
- return { components, props, validations };
925
+ return { components, props, validations, names };
831
926
  }
832
927
  export function validationNote(fixed) {
833
928
  return fixed.length === 0 ? [] : [`validation gövdeleri düzeltildi (${fixed.join(' · ')})`];
@@ -1800,7 +1895,7 @@ export function rootAddsNote(retargeted, containerElementId) {
1800
1895
  export function componentDefaultsNote(fixed) {
1801
1896
  if (fixed.length === 0)
1802
1897
  return [];
1803
- return [`komponent gövdelerine eksik zorunlu alanlar eklendi (${fixed.join(' · ')}): bunlar olmadan platform NullReferenceException verir ve page_schema_validate bunu yakalamaz`];
1898
+ return [`komponent gövdelerine eksik varsayılan alanlar eklendi (${fixed.join(' · ')}): bunlar olmadan platform NullReferenceException verir ve page_schema_validate bunu yakalamaz. blocks eklenmez — gövdede olmalı (PAGE-037)`];
1804
1899
  }
1805
1900
  /**
1806
1901
  * Create yolu junction kaydını açar ama fiziksel tabloyu kurmaz (platform bug'ı P-01,
@@ -81,6 +81,68 @@ export function actionScript(action) {
81
81
  var finish = function (result) { return result; };
82
82
 
83
83
  var combobox = node.querySelector('[role="combobox"], .ng-select__value-container, .ng-multiselect__value-container');
84
+
85
+ // SA-12 — autocomplete girdisi de role="combobox" taşır ama açılır kutu değildir: listesi yalnız
86
+ // input olayıyla açılır (tıklama hiçbir şey yapmaz), öneri satırı role="option" ve data-value
87
+ // TAŞIMAZ, seçim satırın mousedown'ındadır (Autocomplete.svelte on:mousedown|preventDefault;
88
+ // click ölçüldü, seçmedi). Dropdown yoluna girdiğinde 3 sn bekleyip "kutu devre dışı" diyordu.
89
+ // İstenen metin yazılır ve GÖRÜNEN METNİ eşleşen satıra mousedown atılır; value ile seçim yoktur.
90
+ var suggest = combobox && combobox.closest('.ng-autocomplete');
91
+ if (suggest) {
92
+ // Vurgu satırı [metin, ' ', <strong>, ' ', metin] düğümleriyle basılır (Svelte derleyicisi):
93
+ // textContent 'Ist anbul' olur. Yalnız-boşluk düğümleri atlanınca etiket geri gelir.
94
+ var labelOf = function (option) {
95
+ var label = option.querySelector('.ng-autocomplete__item-label');
96
+ if (label) return Array.prototype.map.call(label.childNodes, function (part) { return part.nodeType === 3 && !/\\S/.test(part.textContent) ? '' : part.textContent; }).join('').trim();
97
+ var caption = option.querySelector('.ng-autocomplete__item-caption');
98
+ var text = option.textContent || '';
99
+ return (caption ? text.replace(caption.textContent || '', '') : text).trim();
100
+ };
101
+ var same = function (left, right) { return left.replace(/\\s+/g, ' ').trim().toLowerCase() === right.replace(/\\s+/g, ' ').trim().toLowerCase(); };
102
+
103
+ Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value').set.call(combobox, wanted);
104
+ combobox.dispatchEvent(new Event('input', { bubbles: true }));
105
+
106
+ return new Promise(function (resolve) {
107
+ var waited = 0;
108
+ var step = 100;
109
+ var limit = 3000;
110
+
111
+ var attempt = function () {
112
+ var options = Array.prototype.slice.call(suggest.querySelectorAll('[role="option"]'));
113
+ var match = options.filter(function (option) { return same(labelOf(option), wanted); })[0];
114
+
115
+ if (match) {
116
+ match.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true }));
117
+ resolve(finish({ found: true, applied: true, matchedBy: 'text', optionsSeen: options.length, waitedMs: waited, requests: count }));
118
+ return;
119
+ }
120
+
121
+ if (waited >= limit) {
122
+ var empty = suggest.querySelector('.ng-autocomplete__empty');
123
+ var seen = options.slice(0, 10).map(labelOf).join(' · ');
124
+ var shown = options.length > 0
125
+ ? "görülen " + options.length + " öneri: " + seen
126
+ : empty
127
+ ? "liste: '" + (empty.textContent || '').trim() + "'"
128
+ : "öneri listesi hiç basılmadı — yazılan metin minChars eşiğinin altında ya da alan kilitli olabilir (page_runtime_get ile minChars/isDisabled/isReadonly)";
129
+ var result = { found: true, applied: false, waitedMs: waited, requests: count, note: "öneri listesinde '" + wanted + "' yok (" + waited + " ms) — " + shown + ". Yazılan metin ('" + wanted + "') alanda kaldı. Autocomplete yalnız görünen metinle seçer, value ile değil; öneriler bir sorgudan geliyorsa query_preview ile doğrulayın." };
130
+ // optionsSeen: 0 yazılmaz — page_screenshot sıfır seçenekli 'select' için açılır kutuya
131
+ // ait "devre dışı ya da salt okunur olabilir" notunu basar (tools/pages.ts emptyOptions);
132
+ // bu dalın nedeni yukarıdaki nottadır.
133
+ if (options.length > 0) result.optionsSeen = options.length;
134
+ resolve(finish(result));
135
+ return;
136
+ }
137
+
138
+ waited += step;
139
+ setTimeout(attempt, step);
140
+ };
141
+
142
+ attempt();
143
+ });
144
+ }
145
+
84
146
  if (combobox) {
85
147
  // Liste BU kutunun kabının içindedir. Sorgular belge kapsamlı kaldığında açık duran başka
86
148
  // bir kutunun (multiselect seçimden sonra kapanmıyor) seçeneğine tıklanabiliyordu ve adım
@@ -221,9 +283,12 @@ export function actionScript(action) {
221
283
  // Açılır kutu (dropdown / multi-select) NATIVE alan değildir: değeri yazmak hiçbir şey
222
284
  // yapmaz, seçeneğe TIKLANMASI gerekir. 'set' bu noktada 'select' fiiliyle AYNI yola girer —
223
285
  // iki kopya seçim mantığı tutulmaz.
286
+ //
287
+ // SA-12 — autocomplete girdisi de role="combobox" taşır ama serbest metin alanıdır: listesi
288
+ // tıklamayla değil input olayıyla açılır. Native yola düşer; öneri seçmek 'select'in işi.
224
289
 
225
290
  var combobox = node.querySelector('[role="combobox"], .ng-select__value-container, .ng-multiselect__value-container');
226
- if (combobox) {
291
+ if (combobox && !combobox.closest('.ng-autocomplete')) {
227
292
  // Liste BU kutunun kabının içindedir. Sorgular belge kapsamlı kaldığında açık duran başka
228
293
  // bir kutunun (multiselect seçimden sonra kapanmıyor) seçeneğine tıklanabiliyordu ve adım
229
294
  // "uygulandı" diyordu; komşunun yükleme göstergesi de bu kutunun kararını blokluyordu (B-5).
@@ -57,9 +57,9 @@ Doğrulama mesajını çeviriye bağlamak ayrı bir sözleşmedir (`t:` öneki,
57
57
 
58
58
  ## Olay modeli
59
59
 
60
- - Olay adları `component_get.events`'ten okunur, **`on` öneki almaz** (tek istisna
61
- `texteditor`: onMount/onChange). Uydurma ada bağ sessizce ölü kalır — gateway PAGE-008 ile
62
- reddeder.
60
+ - Olay adları `component_get.events`'ten okunur, **`on` öneki almaz** (`texteditor` dahil:
61
+ `mount`/`change`/`changed`/`focus`/`blur`). Uydurma ada bağ sessizce ölü kalır — gateway
62
+ PAGE-008 ile reddeder.
63
63
  - Yük `events[].payload`'dadır ama akışa **`event.data` ile GELMEZ**: motor yükü komponentin
64
64
  KENDİ prop'una yazar (`FlowMachine.onEmit` → `setNode`) ve akışı emit'in `params`'ıyla başlatır.
65
65
  Yani `payload` anahtarları prop adlarıdır ve `self.<anahtar>` ile okunur (ya da
@@ -290,9 +290,11 @@ create step'inde aynı kural geçerlidir:
290
290
  "ticket_no": "TLP-0002"
291
291
  ```
292
292
 
293
- Girdi sınırları (`IncrementalCodeInput.TryParse`; ihlal edilirse nesne geçersiz sayılır ve kod
294
- üretilmez): `Prefix` ≤ 3 karakter · `Separator` ≤ 1 karakter · `Counter` ≥ 1 ·
295
- `DigitCount` 1–12.
293
+ Girdi sınırları (`IncrementalCodeInput.TryParse`): `Prefix` ≤ 3 karakter · `Separator` ≤ 1
294
+ karakter · `Counter` ≥ 1 · `DigitCount` 1–12. İhlal edilirse yazma 400
295
+ `InvalidParameterException` ile düşer ve satır yazılmaz (`EntityService.ApplyIncrementalValuesAsync`;
296
+ tablo kaynaklı uçta ölçüldü 2026-10-02). `row_next_code` bu sınırları DENETLEMEZ, aşan şekli de
297
+ kodla döndürür. Nesnedeki `FieldId` yazılan alanın id'si olmalı, değilse aynı 400.
296
298
 
297
299
  - **Sayaç kendini onarır:** üretim var olan en yüksek koddan devam eder
298
300
  (`EntityIncrementSequenceService.ResolveStartingCounterAsync`), yani elle `TLP-0005` yazdıktan
@@ -4,6 +4,17 @@ Studio'nun tuval tarafı. Buradaki kurallar controller + komut kodundan doğrula
4
4
  (`zeyrek-mcp/docs/13-faz2-envanter.md` §4); veri/mantık tarafı için
5
5
  `platform_guide('entity')`, `('microservice')`, `('workflow')`.
6
6
 
7
+ **Asgari komponent gövdesi** — her yazma yolunda, her düğümde (çocuklar dahil); ayrıntı Kural 1:
8
+
9
+ ```jsonc
10
+ { "elementId": "btn_kaydet", "name": "btn_kaydet", "type": "button",
11
+ "props": { "text": { "type": "static", "data": "Kaydet" } },
12
+ "blocks": [{ "breakpoint": 1024, "size": { "w": 3, "h": 4 }, "position": { "x": 0, "y": 0 } }] }
13
+ ```
14
+
15
+ `blocks` zorunlu, gateway doldurmaz (yoksa istek PAGE-037 ile reddedilir); `name` = `elementId`
16
+ (ifadelerde `name` okunur). Flex kapta `position` yalnız sıradır, ızgarada gerçek hücredir.
17
+
7
18
  ## Kuruluş sırası
8
19
 
9
20
  ```
@@ -32,7 +43,7 @@ mümkündür ama doğrulama duvarı ve canlı canvas yalnız `page_schema_patch`
32
43
 
33
44
  ```jsonc
34
45
  { "elementId": "page_header_1", "kind": "component", "type": "<component_list slug'ı>",
35
- "name": "Page header",
46
+ "name": "page_header_1",
36
47
  "props": { "pageTitle": { "type": "static", "data": "Branşlar" } },
37
48
  "states": { "isSticky": false, "isDroppable": false, "isScrollable": false, "isWrapper": false },
38
49
  "blocks": [{ "breakpoint": 1024, "size": { "h": 4, "w": 12 }, "position": { "x": 0, "y": 0 } }],
@@ -74,6 +85,10 @@ mümkündür ama doğrulama duvarı ve canlı canvas yalnız `page_schema_patch`
74
85
  `Data = RedirectPage.Slug`). Sabit `'/hastalar'` yazarsan sayfa slug'ı değişince bağlantı
75
86
  sessizce kırılır; `pageId` referansı değişiklikten etkilenmez. Aynısı `flow` prop'u için
76
87
  `type: 'flow'` + `ref: <flowId>`, dosya için `type: 'file'` + `ref: <fileId>`.
88
+ İSTİSNA: form dosya alanının (fileInput / filebutton / fileUpload) `value`'suna `type: 'file'` bağlama — çip
89
+ URL yazar ve kullanıcı dokunmadan kaydederse tekil FILE alanı sessizce NULL'lanır (ölçüldü 2026-10-02). Varsayılan
90
+ dosya için `{ type: 'static', data: { type: 'ref', value: <fileId>, fileName, absolute_path } }` ver (`component_get`
91
+ notu).
77
92
  - **Çoklu dilli metin için `type: 'translation'` + `ref: <çeviri anahtarı>`.** Runtime `data`'yı
78
93
  her turda çözümlenmiş metinle EZER — anahtar yalnız `ref`'te yaşar, `data`'ya yazıp
79
94
  güvenme (`code` prop'uyla aynı davranış). Tam şekil, fallback zinciri ve `translation_*`
@@ -119,20 +134,32 @@ mümkündür ama doğrulama duvarı ve canlı canvas yalnız `page_schema_patch`
119
134
  kaydırıldı (…)"` ile bildiriyor. Kökteki `modal`/`drawer` katmanları sayfa akışında durmadığı için
120
135
  hariç. Aynı hücreyi paylaşan komponentlerden hangisinin üstte kalacağı garanti değil — konumu sen
121
136
  doğru ver, düzeltme yalnız emniyet kemeri.
122
- - **Zorunlu alanlar:** `states` her yolda, `events` / `validations` / `stateBindings` ise
123
- **micro-op yolunda** (`page_schema_patch`). `states` için sebep açık:
124
- `DesignComponentManagementSchema.ToDomain:47` guard'sız okur, `Appearance`'ın varsayılanı var
125
- `States`'in yok. Diğer üçünü `page_create` (POST /pages) tolere ediyor ama `PATCH …/schema`
126
- etmiyor — canlı deneyde üçü **birlikte** verilince geçti, her biri **tek başına** verilince
127
- `NullReferenceException` aldı. **Gateway hepsini senin yerine dolduruyor**
137
+ - **Zorunlu alanlar:** `elementId`, `name`, `type` ve `blocks` her düğümde senin işin;
138
+ `blocks` (gateway doldurmaz) yoksa ya da `null`'sa platform kayıtta 500
139
+ `NullReferenceException` verir, gateway bu yüzden isteği göndermeden **PAGE-037** ile reddeder
140
+ (`blocks: []` reddedilmez). `name` = `elementId` yaz; boş bırakırsan gateway elementId'yi ad
141
+ olarak yazar ve bildirir (Y-15). `states` her yolda, `events` / `validations` /
142
+ `stateBindings` ise **micro-op yolunda** gerekir: `states`'i
143
+ `DesignComponentManagementSchema.ToDomain:47` guard'sız okur; diğer üçünü `page_create`
144
+ tolere ediyor ama `PATCH …/schema` onlarsız `NullReferenceException` alıyor (canlı deney).
145
+ **Bunları (ve `children`'ı) gateway senin yerine dolduruyor**
128
146
  (`page_create`/`page_update`/`page_schema_patch`, `layout_create/update`,
129
- `component_create/update`) ve yanıtta `gatewayNormalized` ile bildiriyor; yani elle yazman
130
- gerekmiyor, ama ham HTTP kullanıyorsan bu farkı bilmen gerekir.
131
-
132
- > **`page_schema_validate`'e tek başına güvenme.** `ValidatePageSchemaQuery` yalnız micro-op'ları
133
- > uygulayıp şema kurallarını denetler, `ToDomain`'e hiç girmez — eksik `states`'li bir gövdeye
134
- > `{"valid":true}` der, yazma yolu ise patlar. Gövde tamamlandığında ise doğrulayıcı işini
135
- > düzgün yapar (boyut/hedef hatalarını açık mesajla verir).
147
+ `component_create/update`) ve yanıtta `gatewayNormalized` ile bildiriyor — `blocks`'u değil.
148
+ - **`blocks` tarifi (tek kural):** her düğüme
149
+ `[{ "breakpoint": 1024, "size": { "w", "h" }, "position": { "x", "y" } }]` ver; `size`'ı
150
+ `component_get` çıktısındaki `sizes`'tan al. Konumun anlamı **ebeveynin** `layout`'una bağlıdır:
151
+ - flex kap (`layout: 'flex'`): `position` yalnız sırayı belirtir (önce `y`, sonra `x`). Sırayı
152
+ `index` ile de verebilirsin — `page_schema_patch` kardeşlerin konumunu görünen sıraya göre
153
+ yeniden yazar (Kural 1f-2) — ama `blocks` yine gövdede olur.
154
+ - ızgara kap (`layout: 'grid'` ya da `layout` prop'u yok): `position` gerçek hücredir; sona
155
+ eklemek için `x: 0`, `y`: kardeşlerin en büyük `y + h` değeri. Aynı hücre üst üste biner.
156
+ - `blocks: []` kaydolur ama runtime 1024 bloğu bulamaz ve yerleşim sessizce bozulur — verme.
157
+
158
+ > **`page_schema_validate`'in platform ayağına tek başına güvenme.** `ValidatePageSchemaQuery`
159
+ > `ToDomain`'i koşar ama `NullReferenceException` kayıt adımında çıkar — eksik `states`'li ya da
160
+ > `blocks`'suz bir gövdeye platform `{"valid":true}` der, yazma yolu ise patlar. Gateway eksik
161
+ > varsayılanları kuru koşuda da doldurur, `blocks`'suz düğümü (PAGE-037) ve bozuk adı (PAGE-009)
162
+ > yazmayla aynı biçimde reddeder.
136
163
 
137
164
  ## Kural 1b — `page_build` YOK
138
165
 
@@ -164,8 +191,8 @@ Yani "satırı seçince detay getir" (`select`), "kolondan süzünce sunucudan y
164
191
  yalnız `mount` yayar (bilinçli — Kural 1e); `page-header` 2026-09-18'den beri izdeki öğe
165
192
  tıklamasını `clickItem` olarak yayar (`component_get` yükü verir).
166
193
 
167
- **Olay adları `on` ÖNEKİ ALMAZ** (`button` → `click`, `form` → `submit`), tek istisna katalogda
168
- `texteditor`'dur (`onMount`/`onChange`). **Komponentin desteklemediği bir olay adı verirsen bağ
194
+ **Olay adları `on` ÖNEKİ ALMAZ** (`button` → `click`, `form` → `submit`, `texteditor` → `change`);
195
+ katalogda istisna yok. **Komponentin desteklemediği bir olay adı verirsen bağ
169
196
  sessizce ölü kalır** — önce `component_get`. Gateway `set_event`'i katalogla karşılaştırır ve
170
197
  uymayan adı **PAGE-008** ile istek gitmeden reddeder.
171
198
 
@@ -610,9 +637,9 @@ Runtime, ifade çalıştırmadan önce sayfadaki **her** komponent için bir de
610
637
  const <komponentin name'i> = Components["<komponentin name'i>"];
611
638
  ```
612
639
 
613
- `name` geçerli bir JS tanımlayıcısı değilse (boşluk, nokta, birden çok tire…) üretilen satır
614
- sözdizimi hatası olur ve **o sayfadaki BÜTÜN ifadeler** — condition kuralları, `value.type:'code'`
615
- ref'leri, `code` adımları — tek seferde ölür:
640
+ `name` geçerli bir JS tanımlayıcısı değilse (boşluk, nokta, birden çok tire…) ya da hiç yoksa
641
+ üretilen satır sözdizimi hatası olur ve **o sayfadaki BÜTÜN ifadeler** — condition kuralları,
642
+ `value.type:'code'` ref'leri, `code` adımları — tek seferde ölür:
616
643
 
617
644
  ```
618
645
  Expression error: Missing initializer in const declaration in <senin ifaden>
@@ -624,7 +651,12 @@ dönüyor, condition patlıyor, kullanıcı yönlenmiyordu.
624
651
 
625
652
  **Kural:** `name` **her zaman `elementId` ile aynı** olsun (`btn_login`, `tbl_mng`). Okunabilir
626
653
  Türkçe başlık `props.title` / `props.text` / `props.label` / `props.pageTitle` içine yazılır —
627
- `name` bir etiket değil, **değişken adıdır**.
654
+ `name` bir etiket değil, **değişken adıdır**. Gateway eksik/boş adı elementId'den doldurur ve
655
+ bildirir; yine de adı açıkça elementId ile aynı yaz.
656
+
657
+ **Tire:** runtime addan yalnız **ilk** tireyi siler (`name.replace('-', '')`) ve PAGE-009 da aynı
658
+ dönüşümle denetler: `a-b` geçer (`ab` olur), `a-b-c` reddedilir (`ab-c` tanımlayıcı değildir).
659
+ Tire yerine `_` kullan.
628
660
 
629
661
  > **Layout adları her sayfaya sızar.** Layout'un komponentleri de aynı `const` listesine girer;
630
662
  > layout'ta tek bir bozuk ad, o layout'a bağlı **her** sayfayı öldürür. `layout_update` yaparken de
@@ -635,7 +667,9 @@ Türkçe başlık `props.title` / `props.text` / `props.label` / `props.pageTitl
635
667
  >
636
668
  > Gateway bunu **PAGE-009** ile HER sayfa ve layout yazma yolunda istek gitmeden reddeder:
637
669
  > `page_create`, `page_update(schema)`, `add_component` (sayfa, `layout_schema_patch` ve
638
- > `component_schema_patch` yamalarında), `layout_create`, `layout_update(views)` ve `layout_patch`.
670
+ > `component_schema_patch` yamalarında), `layout_create`, `layout_update(views)` ve `layout_patch`;
671
+ > `page_schema_validate` aynı kararı verir. Ad doldurulduktan sonra denetlenir: geçersiz bir
672
+ > elementId adsız gelirse o da reddedilir.
639
673
 
640
674
  ## Kural 1d — Sayfa iskeleti: `page_container` ve boyutlandırma
641
675
 
@@ -856,10 +890,10 @@ kullanır: `page.mappers.ts` `mapPageLayoutToPreview` — layout varsa çizilen
856
890
  > (`page_container` böyledir ve öyle kalmalı). Sadece "başlık/çerçeve çizilmesin" diye wrapper
857
891
  > yapma — o iş `container` için de `form` için de `style` ile yapılır (`background:'transparent'`,
858
892
  > `border:'none'`, `padding:0`). `form`'un eski `hideBorders`/`hidePadding` prop'ları
859
- > 2026-09-09'da şemadan kaldırıldı — bileşen onları hiçbir zaman okumuyordu, no-op'tu. **`states` için micro-op
860
- > YOKTUR:** yanlış `isWrapper`'ı düzeltmenin tek yolu düğümü `remove_component` +
861
- > `add_component` ile yeniden kurmaktır (sonrasında Kural 2'deki flow yeniden kaydetme adımı
862
- > ZORUNLU olur).
893
+ > 2026-09-09'da şemadan kaldırıldı — bileşen onları hiçbir zaman okumuyordu, no-op'tu. Yanlış
894
+ > `isWrapper`'ı düğümü silmeden düzelt: `resize_component { target, states: { isWrapper: false } }`
895
+ > (platforma `set_states` olarak gider). `remove_component` + `add_component` ona yazan akış
896
+ > setter'larını öldürür (`FLOW-018`).
863
897
 
864
898
  Mobil not: <768px'te ızgara tek kolona düşer (`RenderComponent` media query'si) — y sırası
865
899
  dikey sıraya dönüşür, x/w yok sayılır. Yerleşimi masaüstü (breakpoint 1024) bloğuyla kur.
@@ -884,7 +918,8 @@ aynı prop grubunu o da alır. Grup katalogda tanımlıdır, yani şemadan kurul
884
918
 
885
919
  **`form` da bu grubun tamamını taşır (2026-09-01):** varsayılanları container ile aynı
886
920
  (`flex` · `row` · `left` · `top` · `shrink` · `medium` · `wrap` açık). "Form'a layout yazılmaz"
887
- eski bilgidir. (Platform kataloğu güncel şemayı `component_get`'te göstermiyorsa kayıt
921
+ eski bilgidir. Ama varsayılan düğüme yazılmaz (aşağıda): `layout`'u açıkça yazmadığın form
922
+ ızgara çizilir ve çocukları ızgara koordinatıyla dizilir. (Platform kataloğu güncel şemayı `component_get`'te göstermiyorsa kayıt
888
923
  yenilenmemiştir — ARC-024.)
889
924
 
890
925
  `stretch` iki eksende farklı anlama gelir: **ana** eksende `space-between` (çocukları uçlara
@@ -927,13 +962,17 @@ Beş şeyi bilmeden flex kurma:
927
962
  aradaki boşluklar çizilmez. Yine de bitişik ver (0,1,2…): kapsayıcı ileride `grid`'e
928
963
  döndürülürse şema geçerli kalsın. Gateway bu yüzden **flex kapsayıcının İÇİNDE PAGE-010
929
964
  boşluk uyarısı üretmez** (ızgarada üretmeye devam eder).
930
- **Dizi sırası çizimi belirlemez** — `children[]` içindeki yer değil `y` kazanır; `y`'si eşit
931
- iki çocukta dizi sırası kazanır. Bu yüzden flex kapta sıralamanın yolu `index`'tir:
965
+ **Dizi sırası çizimi belirlemez** — `children[]` içindeki yer değil `y` kazanır; iki çocuğa
966
+ aynı `y`'yi verme (ölçümde eşit `y`'li iki çocuk dizi sırasının tersine çizildi, sıra garanti
967
+ değil). Bu yüzden flex kapta sıralamanın yolu `index`'tir:
932
968
  `add_component`/`move_component`'e verilen `index` **görünen sıradaki yeri** belirler ve gateway
933
969
  kardeşlerin konumunu Studio'nun flex tuvali gibi yeniden yazar (x=0, alt alta birikimli y;
934
970
  yanıtın `gatewayNormalized` notu hangi `y`'nin değiştiğini söyler). Grafiğin üstüne başlık:
935
- `{ "op": "add_component", "parent": "<flex kap>", "index": 0, "component": { … } }` — koordinat
936
- hesaplamak gerekmez, `remove_component` + `add_component` hiç gerekmez. **Izgara kapta `index`
971
+ `{ "op": "add_component", "parent": "<flex kap>", "index": 0, "component": { … } }` —
972
+ `component` yine tam gövdedir, `blocks` dahil (verdiğin `position`'ı gateway sıraya göre yeniden
973
+ yazar; koordinat hesaplamak gerekmez ama `blocks`'suz gövde PAGE-037 ile reddedilir),
974
+ `remove_component` + `add_component` hiç gerekmez. Bu çeviriyi yalnız `page_schema_patch` yapar;
975
+ `layout_schema_patch` ve `component_schema_patch`'te sırayı `position.y` verir. **Izgara kapta `index`
937
976
  çizimi değiştirmez** (her çocuk kendi `position`'ında mutlak çizilir): sırayı
938
977
  `move_component { target, position: { x, y } }` ile ver; sona eklemeyen `index` orada
939
978
  **PAGE-033 (uyarı)** üretir.
@@ -983,12 +1022,13 @@ gibi bitişik duracak elemanlar için ÜÇ şart birden gerekir; biri eksikse ar
983
1022
 
984
1023
  ```jsonc
985
1024
  // Bitişik rozet dizisi: kap + iki çocuktan biri (diğeri aynı desen)
986
- { "elementId": "badge_row", "type": "container", "kind": "component",
1025
+ { "elementId": "badge_row", "name": "badge_row", "type": "container", "kind": "component",
987
1026
  "props": { "layout": { "type": "static", "data": "flex" },
988
1027
  "direction": { "type": "static", "data": "row" },
989
1028
  "gap": { "type": "static", "data": "none" } },
1029
+ "blocks": [{ "breakpoint": 1024, "size": { "w": 12, "h": 4 }, "position": { "x": 0, "y": 0 } }],
990
1030
  "children": [
991
- { "elementId": "badge_1", "type": "button", "kind": "component",
1031
+ { "elementId": "badge_1", "name": "badge_1", "type": "button", "kind": "component",
992
1032
  "appearance": { "widthType": "auto", "marginType": "none", "heightType": "auto" },
993
1033
  "blocks": [{ "breakpoint": 1024, "size": { "w": 2, "h": 4 }, "position": { "x": 0, "y": 0 } }] }
994
1034
  ] }
@@ -1056,6 +1096,10 @@ UYGULAMAZ** — koordinatları aynen kaydeder (aşağı itme yalnız `page_creat
1056
1096
  > `{ background:'transparent', border:'none', '&__body': { padding:'0px' } }`.
1057
1097
  > `appearance.isHidden` diye bir alan YOKTUR (2026-09-15'te platformdan kaldırıldı, S-16) —
1058
1098
  > görünürlük yalnız `props.isHidden`'dır.
1099
+ >
1100
+ > **Zorunlu alanı gizli kaba SARMA:** form, yalnız alanın KENDİ `isHidden`'ını muaf tutar. Sarmalayıcı
1101
+ > kap gizliyken içindeki `required` alan doğrulanmaya devam eder, ekranda görünmeyen hatayla gönderimi
1102
+ > kilitler (ölçüldü 2026-10-03). Alanın kendi `isHidden`'ı varsa koşulu ona ver; yoksa alanı zorunlu yapma.
1059
1103
 
1060
1104
  > **Satır BAŞINA doğrulama kurulamaz.** `validations` komponentin kendisindedir, tüm satırlar
1061
1105
  > onu paylaşır — "3. soru zorunlu, 4. soru değil" şemayla ifade edilemez. Zorunluluk satır
@@ -1472,9 +1516,9 @@ boş görünür**; kayıt tarafında hiçbir hata çıkmaz (kütüphane turu R1)
1472
1516
 
1473
1517
  ```jsonc
1474
1518
  "views": [
1475
- { "elementId": "app_header", "kind": "component", "type": "header", "props": { … },
1519
+ { "elementId": "app_header", "name": "app_header", "kind": "component", "type": "header", "props": { … },
1476
1520
  "blocks": [{ "breakpoint": 1024, "size": { "h": 8, "w": 12 }, "position": { "x": 0, "y": 0 } }] },
1477
- { "elementId": "content_slot", "kind": "component", "type": "slot", "name": "İçerik",
1521
+ { "elementId": "content_slot", "kind": "component", "type": "slot", "name": "content_slot",
1478
1522
  "props": {}, "blocks": [{ "breakpoint": 1024, "size": { "h": 80, "w": 12 }, "position": { "x": 0, "y": 8 } }] }
1479
1523
  ]
1480
1524
  ```
@@ -1861,7 +1905,7 @@ bağlanır, filtre `source:'expression'` + `value:'user.id'` olur.
1861
1905
  | İki durumlu | `checkbox` · `switch` · `switcher` (iki durum yan yana) |
1862
1906
  | Puan / kod girişi | `rating` · `otpinput` |
1863
1907
  | Tarih / saat / aralık | `datepicker` · `datetimepicker` · `daterangepicker` · `datetimerangepicker` · `timepicker` (yalnız HH:mm) |
1864
- | Kimlik alanları | `email` · `password` |
1908
+ | Kimlik alanları | `textinput` (e-posta için `inputMask` Email hazır maskesi ya da `regex` kuralı — `email` diye bir bileşen YOK, `add_component` `unknown-type` ile reddedilir) · `password` |
1865
1909
  | Dosya | `fileUpload` (dropzone) · `fileInput` · `filebutton` · `file-viewer` |
1866
1910
  | Liste | `table` · `datatable` (Kural 1e) · `repeater` (kart listesi VE dinamik form — Kural 1g) · `tree-view` (hiyerarşi; `isSearchable` ile istemci içi arama) · `timeline` (tarihli olay akışı) |
1867
1911
  | Pano / takvim | `kanban` (durum kolonlarında sürüklenebilir kartlar; taşıma `cardMove` ile `event.data`'dan okunur, kalıcılık AKIŞIN işidir) · `eventcalendar` (ay/hafta/gün/ajanda; veri `rows`'a akışla yazılır, `monthChange`/`viewChange` görünen `start`/`end` aralığıyla tazeleme kancasıdır) |
@@ -1870,7 +1914,7 @@ bağlanır, filtre `source:'expression'` + `value:'user.id'` olur.
1870
1914
  | İmza / CSV içe aktarma | `signature` (değer PNG data-URL ya da ham SVG; `change` setter'lı) · `importwizard` (3 adımlı sihirbaz; temiz+hatalı satırlar `complete` yükünde — satırları uca yazmak akışın işidir) |
1871
1915
  | Sayfalama (tablo dışı listeler için) | `pagination` — offset/limit yazar, `self.offset`/`self.limit` ile sorguya bağlanır |
1872
1916
  | Grafik / KPI | `chart` (ECharts: bar·line·area·pie·donut + waterfall/histogram/pareto/bullet/tornado/radial) · `statcard` (tek KPI + trend + sparkline) · `progress` |
1873
- | Kapsayıcı / düzen | `container` (ızgara ya da flex — Kural 1f-2) · `form` (düzen prop'u YOK, çocuklar ızgara koordinatıyla dizilir) · `tabs` · `collapsible-container` · `accordion` |
1917
+ | Kapsayıcı / düzen | `container` (ızgara ya da flex — Kural 1f-2) · `form` (container ile aynı düzen prop'ları; `layout` yazılmazsa ızgara — Kural 1f-2) · `tabs` · `collapsible-container` · `accordion` |
1874
1918
  | Kart görünümü | `card` — **kapsayıcı DEĞİLDİR**, çocuk kabul etmez (`not-droppable`) ve yüksekliği 10-50 aralığındadır. Kutu içinde serbest içerik için `container` kullan. |
1875
1919
  | Katman | `modal` · `drawer` (şema kökünde — Kural 1d) |
1876
1920
  | Geri bildirim | `notification` (sayfa içi kutu) · `toast` (geçici bildirim; isOpen yükselen kenarı) · `form-validation-errors` · `confirm-bar` · `result` (işlem sonucu ekranı) · `emptystate` (boş liste; ikon YA DA görsel + aksiyon düğmeleri) |
@@ -1,5 +1,5 @@
1
1
  import { guid, projectParam, environmentParam, readOnly, writes, updates, destructive } from './common.js';
2
- import { normalizeComponentDefaults, componentDefaultsNote, clampBlockSizes, blockSizeNote, normalizeHeightTypes, heightTypeNote, normalizeSizeStrings, sizeStringNote, withNormalization } from '../knowledge/normalize.js';
2
+ import { normalizeComponentDefaults, componentDefaultsNote, assertComponentBlocks, clampBlockSizes, blockSizeNote, normalizeHeightTypes, heightTypeNote, normalizeSizeStrings, sizeStringNote, withNormalization } from '../knowledge/normalize.js';
3
3
  import { eventContractsFor, eventPayloadNote } from '../knowledge/componentEvents.js';
4
4
  import { componentPresetDefaults, readProjectTheme } from '../knowledge/themeSchema.js';
5
5
  import { knowledgeFor, knowledgeNote, APPEARANCE_TOKEN_SETS, STATE_FLAG_NOTES, INTERNAL_ROLES_BY_TYPE } from '../knowledge/componentCatalog.js';
@@ -270,6 +270,10 @@ export function registerComponentTools(server, platform) {
270
270
  },
271
271
  annotations: updates
272
272
  }, handle(config, async ({ project, environment, applicationId, slug, ...changes }) => {
273
+ // PAGE-037: UpdateComponentCommand de UpsertRange'ten geçer, blocks'suz widget düğümü kayıtta
274
+ // 500 verir. component_create bu kapıdan geçmez — CreateComponentCommand revizyon kapsamı
275
+ // açmıyor ve platform orada blocks'suz düğümü blocks: [] gibi kaydediyor (2026-10-04, kod okuması).
276
+ assertComponentBlocks(changes.schema);
273
277
  const components = await listComponents(platform, project, environment, applicationId);
274
278
  const current = components.find(entry => entry.slug === slug);
275
279
  if (!current) {
@@ -1,4 +1,4 @@
1
- import { orphanedFlowWarnings, orphanedFlowPlan, orphanedFlowMessage, boundEventOps, stripGatewayEventFields, staleSetterWarnings, normalizeSchemaOps, validationNote, componentDefaultsNote, replacePropNote, flexSizeWarnings, nestedRepeaterNodes, nestedRepeaterMessage, stringStyleNodes, stringStyleWarning, inertFlexPropNodes, inertFlexPropWarning, verticalGapWarnings, withNormalization, FLEX_LAYOUT_PROP_NAMES } from '../knowledge/normalize.js';
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
2
  import { opsParam, writeViewParam, outlineWriteResponse, unknownEventOps, collectTypes, simulateLayoutOps, removeNodeFromTree, planEventRemovals, planResizes, planPropTypes, assertOpBodies } from './schemaOps.js';
3
3
  import { guid, projectParam, environmentParam, updates, microOps } from './common.js';
4
4
  import { fetchHost, hostPath } from './screens.js';
@@ -61,6 +61,7 @@ export function registerDesignSchemaPatchTool(server, platform, host) {
61
61
  const opRecords = allOps.filter(entry => !['remove_event', 'resize_component', 'set_prop_type'].includes(String(entry.op)));
62
62
  const normalized = normalizeSchemaOps(opRecords);
63
63
  assertOpBodies(opRecords, propTypeOps);
64
+ assertAddComponentIds(opRecords);
64
65
  const record = await fetchHost(platform, host, project, environment, applicationId, id);
65
66
  // Platform ucu kaydın türüne bakmaz: sıradan bir komponentin id'si verilirse kataloğundaki
66
67
  // tanımın yanına bir şema yazılırdı.
@@ -155,6 +156,7 @@ export function registerDesignSchemaPatchTool(server, platform, host) {
155
156
  }
156
157
  }
157
158
  return withWriteDiff(withNormalization(response, [
159
+ ...componentNameNote(normalized.names),
158
160
  ...sectionNotes,
159
161
  ...componentDefaultsNote(normalized.components),
160
162
  ...validationNote(normalized.validations),
@@ -2,7 +2,7 @@ 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 { normalizeComponentDefaults, componentDefaultsNote, flexSizeWarnings, spacingLayerWarnings, verticalGapWarnings, withNormalization } from '../knowledge/normalize.js';
5
+ import { fillComponentNames, componentNameNote, normalizeComponentDefaults, componentDefaultsNote, flexSizeWarnings, spacingLayerWarnings, verticalGapWarnings, withNormalization } from '../knowledge/normalize.js';
6
6
  import { fetchHost, layoutUpdateBody, schemaParam, eventsParam, modeParam, summarize, findNode } from './screens.js';
7
7
  import { assertScreenWriteGuards, screenCatalogNotes } from './screenWrite.js';
8
8
  import { applyPropTypesToSchema, isSetPropTypeOp, setPropTypeErrors } from '../knowledge/propTypes.js';
@@ -12,7 +12,7 @@ import { handle } from '../platform/responses.js';
12
12
  import { z } from 'zod';
13
13
  const patchOperationsParam = z.array(z.record(z.unknown())).min(1)
14
14
  .describe("JSON Patch listesi (RFC 6902): { op, path, value }. Yol seçici alabilir — '/views/0/children[elementId=baslik]/props/content/data'.");
15
- const viewsParam = schemaParam.describe("Layout'un komponent ağacı. 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).");
15
+ 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).");
16
16
  function countSlots(nodes) {
17
17
  if (!Array.isArray(nodes))
18
18
  return 0;
@@ -90,6 +90,7 @@ export function registerLayoutTools(server, platform) {
90
90
  },
91
91
  annotations: writes
92
92
  }, handle(config, async ({ project, environment, applicationId, name, views, events, parameters }) => {
93
+ const named = fillComponentNames(views);
93
94
  assertScreenWriteGuards('layout', { schema: views, events });
94
95
  const fixed = normalizeComponentDefaults(views);
95
96
  const catalogNotes = await screenCatalogNotes(platform, project, environment, applicationId, views);
@@ -99,7 +100,7 @@ export function registerLayoutTools(server, platform) {
99
100
  body: { name, views: views ?? [], events: events ?? [], parameters: parameters ?? null }
100
101
  });
101
102
  const drops = writeDiff({ name, views: views ?? [], events: events ?? [] }, response);
102
- const normalized = withWriteDiff(withNormalization(response, [...componentDefaultsNote(fixed), ...catalogNotes, ...slotWarning(views)]), drops);
103
+ const normalized = withWriteDiff(withNormalization(response, [...componentNameNote(named), ...componentDefaultsNote(fixed), ...catalogNotes, ...slotWarning(views)]), drops);
103
104
  if (gaps.length === 0 || !normalized || typeof normalized !== 'object')
104
105
  return normalized;
105
106
  return { ...normalized, layoutWarnings: gaps };
@@ -120,6 +121,7 @@ export function registerLayoutTools(server, platform) {
120
121
  annotations: updates
121
122
  }, handle(config, async ({ project, environment, applicationId, id, ...changes }) => {
122
123
  const overrides = Object.fromEntries(Object.entries(changes).filter(([, value]) => value !== undefined));
124
+ const named = fillComponentNames(overrides.views);
123
125
  assertScreenWriteGuards('layout', { schema: overrides.views, events: overrides.events });
124
126
  const current = await fetchHost(platform, 'layout', project, environment, applicationId, id);
125
127
  const fixed = normalizeComponentDefaults(overrides.views);
@@ -130,7 +132,7 @@ export function registerLayoutTools(server, platform) {
130
132
  const summary = { id, changed: Object.keys(overrides), preserved: { viewNodes: body.views.length } };
131
133
  if (gaps.length > 0)
132
134
  summary.layoutWarnings = gaps;
133
- return withNormalization(summary, [...componentDefaultsNote(fixed), ...catalogNotes, ...slotWarning(body.views)]);
135
+ return withNormalization(summary, [...componentNameNote(named), ...componentDefaultsNote(fixed), ...catalogNotes, ...slotWarning(body.views)]);
134
136
  }));
135
137
  server.registerTool('layout_patch', {
136
138
  title: "Layout'a patch uygula",
@@ -180,6 +182,7 @@ export function registerLayoutTools(server, platform) {
180
182
  const overrides = Object.fromEntries(['name', 'views', 'events', 'parameters']
181
183
  .filter(key => patched[key] !== undefined)
182
184
  .map(key => [key, patched[key]]));
185
+ const named = fillComponentNames(overrides.views);
183
186
  assertScreenWriteGuards('layout', { schema: overrides.views, events: overrides.events });
184
187
  const fixed = normalizeComponentDefaults(overrides.views);
185
188
  const catalogNotes = await screenCatalogNotes(platform, project, environment, applicationId, overrides.views);
@@ -206,6 +209,7 @@ export function registerLayoutTools(server, platform) {
206
209
  if (gaps.length > 0)
207
210
  summary.layoutWarnings = gaps;
208
211
  return withNormalization(summary, [
212
+ ...componentNameNote(named),
209
213
  ...componentDefaultsNote(fixed),
210
214
  ...catalogNotes,
211
215
  ...slotWarning(body.views),
@@ -1,9 +1,9 @@
1
1
  import { guid, projectParam, environmentParam, pagingParams, readOnly, writes, updates, destructive, microOps, isBranchName } from './common.js';
2
- import { orphanedFlowWarnings, orphanedFlowPlan, orphanedFlowMessage, boundEventOps, stripGatewayEventFields, staleSetterWarnings, lostSetterTargets, normalizeComponentDefaults, normalizeSchemaOps, schemaOpValidationErrors, codePropTemplateErrors, validationNote, componentDefaultsNote, replacePropNote, ensurePageContainer, retargetRootAdds, rootAddsNote, flexSizeWarnings, spacingLayerWarnings, reorderSchemaBlocks, reorderNote, nestedRepeaterNodes, nestedRepeaterMessage, stringStyleNodes, stringStyleWarning, inertFlexPropNodes, inertFlexPropWarning, FLEX_LAYOUT_PROP_NAMES, resetPageContainerPosition, pageContainerPositionWarning, verticalGapWarnings, flexIndexOrder, flexIndexOrderNote, withNormalization, parseVariableEntries, PAGE_CONTAINER_ELEMENT_ID } from '../knowledge/normalize.js';
2
+ import { orphanedFlowWarnings, orphanedFlowPlan, orphanedFlowMessage, boundEventOps, stripGatewayEventFields, staleSetterWarnings, lostSetterTargets, fillComponentNames, componentNameNote, normalizeComponentDefaults, normalizeSchemaOps, schemaOpValidationErrors, codePropTemplateErrors, addComponentIdErrors, missingBlocksNodes, assertAddComponentIds, assertComponentBlocks, validationNote, componentDefaultsNote, replacePropNote, ensurePageContainer, retargetRootAdds, rootAddsNote, flexSizeWarnings, spacingLayerWarnings, reorderSchemaBlocks, reorderNote, nestedRepeaterNodes, nestedRepeaterMessage, stringStyleNodes, stringStyleWarning, inertFlexPropNodes, inertFlexPropWarning, FLEX_LAYOUT_PROP_NAMES, resetPageContainerPosition, pageContainerPositionWarning, verticalGapWarnings, flexIndexOrder, flexIndexOrderNote, withNormalization, parseVariableEntries, PAGE_CONTAINER_ELEMENT_ID } from '../knowledge/normalize.js';
3
3
  import { functionSignature } from '../knowledge/functionGuards.js';
4
4
  import { fetchHost, pageUpdateBody, schemaParam, eventsParam, unwrap, modeParam, summarize, findNode, flowPath } from './screens.js';
5
5
  import { fetchComponentEventNames } from './components.js';
6
- import { assertScreenWriteGuards, screenCatalogNotes } from './screenWrite.js';
6
+ import { assertComponentNames, assertScreenWriteGuards, screenCatalogNotes } from './screenWrite.js';
7
7
  import { opsParam, writeViewParam, outlineWriteResponse, unknownEventOps, collectTypes, simulateLayoutOps, planEventRemovals, resizeServerOps, planResizes, planPropTypes, rejectedOpsMessage } from './schemaOps.js';
8
8
  import { formBindingWarnings, formSubmitButtonWarnings } from '../knowledge/coverage.js';
9
9
  import { emptyWriteDiff, schemaOpDrops, unwrapProps, withWriteDiff, writeDiff } from '../platform/writeDiff.js';
@@ -229,7 +229,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
229
229
  to: z.string().min(1).optional().describe("Yalnız 'drag': bırakma hedefinin elementId'si — kanban kolonu '<elementId>__col-<value>', takvim günü '<elementId>__day-<yyyy-MM-dd>', tüm gün hücresi '<elementId>__allday-<yyyy-MM-dd>', saat dilimi '<elementId>__slot-<yyyy-MM-dd>T<HH>'"),
230
230
  value: z.unknown().optional().describe("'set' için yazılacak değer. Tarih kutularına ISO yazın ('2026-08-01'); çok alanlı tarih aralığında kök DEĞİL kutu hedeflenir: '<elementId>__start' / '<elementId>__end'. 'tab' için sekmenin ANAHTARI ('iot') ya da görünen metni; 'select' için seçeneğin value'su ya da görünen metni"),
231
231
  ms: z.number().int().min(0).max(15000).optional().describe("'wait' için bekleme süresi")
232
- })).max(20).optional().describe("Kare ALINMADAN ÖNCE sayfada koşturulacak etkileşim adımları: [{action:'set', target:'flt_month', value:'2022-10'}, {action:'click', target:'btn_sign_in'}, {action:'tab', target:'tabs_settings', value:'iot'}, {action:'select', target:'dd_city', value:'34'}, {action:'submit', target:'frm_note'}, {action:'drag', target:'kb_board__card-42', to:'kb_board__col-Assigned'}]. 'tab' SEKME SEÇER: sekme grubunun köküne 'click' göndermek DOM sırasındaki İLK sekmeye gider ve ikinci sekme hiç açılamıyordu — tablist köküne click artık reddedilir ve bu fiile yönlendirir. Eşleme önce '<elementId>__tab-<sekme anahtarı>' kimliğiyle, o yoksa görünen metinle yapılır; actionsReport.matchedBy hangisi olduğunu, actionsReport.activeTab adımdan SONRA hangi sekmenin açık olduğunu söyler. Etkin olmayan sekmenin içeriği DOM'da YOKTUR: o sekmedeki bir düğüme yazmadan önce 'tab' adımı gerekir. 'select' açılır kutudan seçer — kutu native alan değildir, değeri yazmak hiçbir şey yapmaz, seçeneğe tıklanması gerekir; liste 3 sn'ye kadar YOKLANIR (sorgudan beslenen kutuda seçenekler geç basılır) ve actionsReport.optionsSeen + waitedMs neyin görüldüğünü söyler. 'submit' formu gönder butonu OLMADAN gönderir (form.requestSubmit); actionsReport.submitVia hangi yolun kullanıldığını yazar. 'drag' HTML5 sürükle-bırak olaylarını (dragstart→dragover→drop→dragend) hedefin merkezinde atar; kanban cardMove ve takvim eventMove bununla tetiklenir. 'set' native setter kullanır ve input+change olaylarını bubbles ile atar (düz DOM ataması framework'ün duyduğu olayı üretmez); ardından kök düğümün runtime prop'larını yeniden okur: actionsReport.propWritten değerin prop'a ULAŞIP ulaşmadığıdır ve false ise applied da false döner — DOM'a yazılmış ama bileşenin duymadığı değer 'uygulandı' sayılmaz (G-14). Her adımdan sonra yerleşme beklenir ve yanıttaki actionsReport hedefin bulunup bulunmadığını ve adımdan sonra KAÇ AĞ İSTEĞİ çıktığını söyler — 'bağ kuruldu' ile 'çalışıyor' arasındaki farkı ancak bu ayırır (G-24)."),
232
+ })).max(20).optional().describe("Kare ALINMADAN ÖNCE sayfada koşturulacak etkileşim adımları: [{action:'set', target:'flt_month', value:'2022-10'}, {action:'click', target:'btn_sign_in'}, {action:'tab', target:'tabs_settings', value:'iot'}, {action:'select', target:'dd_city', value:'34'}, {action:'submit', target:'frm_note'}, {action:'drag', target:'kb_board__card-42', to:'kb_board__col-Assigned'}]. 'tab' SEKME SEÇER: sekme grubunun köküne 'click' göndermek DOM sırasındaki İLK sekmeye gider ve ikinci sekme hiç açılamıyordu — tablist köküne click artık reddedilir ve bu fiile yönlendirir. Eşleme önce '<elementId>__tab-<sekme anahtarı>' kimliğiyle, o yoksa görünen metinle yapılır; actionsReport.matchedBy hangisi olduğunu, actionsReport.activeTab adımdan SONRA hangi sekmenin açık olduğunu söyler. Etkin olmayan sekmenin içeriği DOM'da YOKTUR: o sekmedeki bir düğüme yazmadan önce 'tab' adımı gerekir. 'select' açılır kutudan seçer — kutu native alan değildir, değeri yazmak hiçbir şey yapmaz, seçeneğe tıklanması gerekir; liste 3 sn'ye kadar YOKLANIR (sorgudan beslenen kutuda seçenekler geç basılır) ve actionsReport.optionsSeen + waitedMs neyin görüldüğünü söyler. Autocomplete'te 'set' metni alana yazar, 'select' metni yazıp öneri satırını GÖRÜNEN METİNLE seçer (value ile değil; eşleşme yoksa yazılan metin alanda kalır ve note bunu söyler). 'submit' formu gönder butonu OLMADAN gönderir (form.requestSubmit); actionsReport.submitVia hangi yolun kullanıldığını yazar. 'drag' HTML5 sürükle-bırak olaylarını (dragstart→dragover→drop→dragend) hedefin merkezinde atar; kanban cardMove ve takvim eventMove bununla tetiklenir. 'set' native setter kullanır ve input+change olaylarını bubbles ile atar (düz DOM ataması framework'ün duyduğu olayı üretmez); ardından kök düğümün runtime prop'larını yeniden okur: actionsReport.propWritten değerin prop'a ULAŞIP ulaşmadığıdır ve false ise applied da false döner — DOM'a yazılmış ama bileşenin duymadığı değer 'uygulandı' sayılmaz (G-14). Her adımdan sonra yerleşme beklenir ve yanıttaki actionsReport hedefin bulunup bulunmadığını ve adımdan sonra KAÇ AĞ İSTEĞİ çıktığını söyler — 'bağ kuruldu' ile 'çalışıyor' arasındaki farkı ancak bu ayırır (G-24)."),
233
233
  language: z.string().min(1).optional().describe("Kareyi BU DİLDE çeker: adrese '?lang=<kod>' eklenir ve runtime dili oradan seçer (öncelik: ?lang → tarayıcıda kayıtlı seçim → tarayıcı dilleri → projenin varsayılanı). Seçim hem çeviriye bağlı prop'ları hem çok dilli alanların satır değerlerini kapsar, yani ikinci dilin karesi için sayfaya dil değiştirici KOYMAK GEREKMEZ — referans tasarımı bozulmadan ikinci dil kanıtlanır. ISO 639-1 iki harfli kod ('tr', 'en'); bölgeli kod ('tr-TR') indirgenir ve indirgeme yanıtta bildirilir. Kod projenin languages listesinde yoksa çağrı REDDEDİLİR: runtime tanımadığı kodu yok sayıp kareyi varsayılan dilde çizerdi ve 'çevrildi' sanılan yanlış bir kare raporlanırdı."),
234
234
  colorScheme: z.enum(['light', 'dark']).optional().describe("Kareyi açık ya da koyu temada çeker. Ziyaretçi tema tercihi çerezde değil localStorage'da yaşar ve tema store'u onu sayfa açılırken bir kez okur; gateway tercihi sayfa script'lerinden ÖNCE eker, bu yüzden sayfada 'theme-toggle' komponenti OLMASA DA koyu tema karesi alınabilir. Verilmezse projenin kendi modu çizilir."),
235
235
  format: z.enum(['png', 'jpeg']).optional().describe("Kare biçimi; varsayılan 'png'. Doğrulama karesi için 'jpeg' + quality 60 bağlamı küçültür (ölçüldü 2026-09-16, aynı sayfa: scale 1'de png 8639 B / jpeg60 8691 B — kazanç yok; scale 0.5'te png 3603 B / jpeg60 3035 B). Asıl kaldıraç ÖLÇEKTİR; renk ve kenar keskinliğinin kendisi inceleniyorsa png'de kalın."),
@@ -570,11 +570,19 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
570
570
  // op'larına çevrilir, platform dokunulan düğümleri aynı kapsamla doğrular.
571
571
  const opRecords = allOps.filter(entry => entry.op !== 'resize_component');
572
572
  const normalized = normalizeSchemaOps(opRecords);
573
+ const added = opRecords.filter(entry => entry.op === 'add_component').map(entry => entry.component).filter(Boolean);
574
+ // page_schema_patch'in sırası: kimlik ve blocks op başına (PAGE-037), sonra ad (PAGE-009).
575
+ assertAddComponentIds(opRecords);
576
+ assertComponentBlocks(added);
577
+ assertComponentNames('page', added);
573
578
  const validationIssues = [
574
579
  ...schemaOpValidationErrors(opRecords).map(issue => `PAGE-016 — ${issue}`),
575
580
  ...codePropTemplateErrors(opRecords).map(issue => `PAGE-032 — ${issue}`)
576
581
  ];
577
582
  const ordering = await orderByIndex(opRecords, () => fetchPage(platform, project, environment, applicationId, pageId));
583
+ // Yazmadaki gibi katalog kuralları GİDEN listeye: kuru koşu kırpılmamış boyutu doğrulayıp
584
+ // yazmanın geçireceği gövdeye hata veriyor, notu da vermiyordu (Y-13 C).
585
+ const catalogNotes = await screenCatalogNotes(platform, project, environment, applicationId, ordering.ops.filter(entry => entry.op === 'add_component').map(entry => entry.component));
578
586
  const response = ((await platform.tenant('POST', project, `/pages/${applicationId}/${pageId}/schema/validate`, {
579
587
  environment,
580
588
  body: { ops: [...allOps.filter(entry => entry.op === 'resize_component').flatMap(resizeServerOps), ...ordering.ops] }
@@ -584,8 +592,10 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
584
592
  if (ordering.warnings.length > 0)
585
593
  response.layoutWarnings = ordering.warnings;
586
594
  return withNormalization(response, [
595
+ ...componentNameNote(normalized.names),
587
596
  ...componentDefaultsNote(normalized.components),
588
597
  ...validationNote(normalized.validations),
598
+ ...catalogNotes,
589
599
  ...replacePropNote(normalized.props),
590
600
  ...flexIndexOrderNote(ordering.moved)
591
601
  ]);
@@ -655,6 +665,8 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
655
665
  reject('PAGE-016', entry, schemaOpValidationErrors([entry]));
656
666
  for (const entry of opRecords)
657
667
  reject('PAGE-032', entry, codePropTemplateErrors([entry]));
668
+ for (const entry of opRecords)
669
+ reject('PAGE-037', entry, [...addComponentIdErrors([entry]), ...(entry.op === 'add_component' ? missingBlocksNodes([entry.component]) : [])]);
658
670
  if (opRecords.some(entry => entry.op === 'set_event' || entry.op === 'add_event')) {
659
671
  const typesByElementId = new Map();
660
672
  try {
@@ -701,7 +713,10 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
701
713
  throw new PlatformApiError(orphanedFlowMessage(deletedFlows), 400);
702
714
  }
703
715
  }
704
- const added = opRecords
716
+ // Katalog kuralları platforma GİDEN listeye uygulanır: index'li yamada `flexIndexOrder` op'ları
717
+ // kopyalar; kırpma ve heightType opRecords'a yazılınca gövdeye girmiyor, not yine "düzeltildi"
718
+ // diyordu (Y-13 C, ölçüldü 2026-10-03: s_all index:0 ile h:2 ve appearance'sız gitti).
719
+ const added = sentOps
705
720
  .filter(entry => entry.op === 'add_component')
706
721
  .map(entry => entry.component);
707
722
  assertScreenWriteGuards('page', { schema: added });
@@ -816,6 +831,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
816
831
  }
817
832
  return withWriteDiff(withNormalization(response, [
818
833
  ...rootAddsNote(retargeted, PAGE_CONTAINER_ELEMENT_ID),
834
+ ...componentNameNote(normalized.names),
819
835
  ...sectionNotes,
820
836
  ...componentDefaultsNote(normalized.components),
821
837
  ...validationNote(normalized.validations),
@@ -860,6 +876,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
860
876
  annotations: writes
861
877
  }, handle(config, async ({ project, environment, applicationId, name, slug, browserTitle, browserTitleKey, layoutId, roleIds, middlewareIds, events, schema, view, ifExists }) => {
862
878
  const container = ensurePageContainer(schema ?? []);
879
+ const named = fillComponentNames(container.schema);
863
880
  assertScreenWriteGuards('page', { schema: container.schema, events });
864
881
  const stringStyles = stringStyleNodes(container.schema);
865
882
  const fixed = normalizeComponentDefaults(container.schema);
@@ -905,7 +922,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
905
922
  };
906
923
  if (updateWarnings.length > 0)
907
924
  summary.layoutWarnings = updateWarnings;
908
- return withNormalization(summary, schema === undefined ? [] : [...container.fixes, ...reset, ...componentDefaultsNote(fixed), ...catalogNotes, ...reorderNote(moved)]);
925
+ return withNormalization(summary, schema === undefined ? [] : [...container.fixes, ...componentNameNote(named), ...reset, ...componentDefaultsNote(fixed), ...catalogNotes, ...reorderNote(moved)]);
909
926
  }
910
927
  const response = await platform.tenant('POST', project, `/pages/${applicationId}`, {
911
928
  environment,
@@ -933,7 +950,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
933
950
  if (view !== 'full' && Array.isArray(saved.schema)) {
934
951
  saved.schema = saved.schema.map(node => summarize(node));
935
952
  }
936
- const normalized = withWriteDiff(withNormalization(withOutcome(response, undefined), [...container.fixes, ...reset, ...componentDefaultsNote(fixed), ...catalogNotes, ...reorderNote(moved)]), drops);
953
+ const normalized = withWriteDiff(withNormalization(withOutcome(response, undefined), [...container.fixes, ...componentNameNote(named), ...reset, ...componentDefaultsNote(fixed), ...catalogNotes, ...reorderNote(moved)]), drops);
937
954
  const createWarnings = [...gaps, ...stringStyleWarning(stringStyles), ...inertFlexPropWarning(inertFlexPropNodes(container.schema)), ...flexSizeWarnings(container.schema, 'PAGE-017 (uyarı)', 'PAGE-018 (uyarı)'), ...spacingLayerWarnings(container.schema, 'PAGE-029 (uyarı)', 'PAGE-030 (uyarı)')];
938
955
  if (createWarnings.length === 0 || !normalized || typeof normalized !== 'object')
939
956
  return normalized;
@@ -955,11 +972,12 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
955
972
  roleIds: z.array(guid).optional(),
956
973
  middlewareIds: z.array(guid).optional(),
957
974
  events: eventsParam.optional(),
958
- schema: schemaParam.optional().describe('Nadiren gerekir; verilmezse mevcut şema korunur')
975
+ schema: schemaParam.optional().describe('Nadiren gerekir; verilirse her düğümde elementId, name (= elementId), type, blocks zorunlu (PAGE-037, gateway blocks doldurmaz); verilmezse mevcut şema korunur')
959
976
  },
960
977
  annotations: updates
961
978
  }, handle(config, async ({ project, environment, applicationId, pageId, ...changes }) => {
962
979
  const overrides = Object.fromEntries(Object.entries(changes).filter(([, value]) => value !== undefined));
980
+ const named = fillComponentNames(overrides.schema);
963
981
  assertScreenWriteGuards('page', { schema: overrides.schema, events: overrides.events });
964
982
  const current = await fetchHost(platform, 'page', project, environment, applicationId, pageId);
965
983
  const fixed = normalizeComponentDefaults(overrides.schema);
@@ -978,7 +996,7 @@ export function registerPageTools(server, platform, injectedScreenshotter, injec
978
996
  };
979
997
  if (gaps.length > 0)
980
998
  summary.layoutWarnings = gaps;
981
- return withNormalization(summary, [...reset, ...componentDefaultsNote(fixed), ...catalogNotes, ...reorderNote(moved)]);
999
+ return withNormalization(summary, [...componentNameNote(named), ...reset, ...componentDefaultsNote(fixed), ...catalogNotes, ...reorderNote(moved)]);
982
1000
  }));
983
1001
  server.registerTool('page_clone', {
984
1002
  title: 'Sayfa klonla',
@@ -1,4 +1,4 @@
1
- import { isFlexContainer, schemaOpValidationErrors, FLEX_LAYOUT_PROP_NAMES, PAGE_CONTAINER_ELEMENT_ID } from '../knowledge/normalize.js';
1
+ import { isFlexContainer, schemaOpValidationErrors, FLEX_LAYOUT_PROP_NAMES, PAGE_CONTAINER_ELEMENT_ID, COMPONENT_BODY_RULE } from '../knowledge/normalize.js';
2
2
  import { findNode, outlineAppliedPatch } from './screens.js';
3
3
  import { setPropTypeErrors } from '../knowledge/propTypes.js';
4
4
  import { PlatformApiError } from '../platform/errors.js';
@@ -24,7 +24,7 @@ export function outlineWriteResponse(response) {
24
24
  if (Array.isArray(container.appliedPatch))
25
25
  container.appliedPatch = outlineAppliedPatch(container.appliedPatch);
26
26
  }
27
- export const opsParam = z.array(z.record(z.unknown())).describe("Micro-op listesi. Op sözlüğü: replace_prop {target, prop, value} (value ham değer, code sarmalı {type: 'code', ref: '{{ … }}'} ya da translation sarmalı {type: 'translation', ref: '<çeviri anahtarı>'} — prop tipini değiştirmek için komponenti silip yeniden EKLEMEYİN, ona yazan setter'ların hedefi silinir (FLOW-018); platform replace_prop'ta expression alanını OKUMAZ — gateway onu code sarmalına çevirir) | set_binding {target, prop, expression: '{{ Page.variables.x }}'} | add_component {parent (üst elementId ya da 'root'), index?, component} | remove_component {target} | move_component {target, parent, index?, position: {x, y}} (index ÇİZİM sırası değildir, runtime çocukları blocks.position'a göre (y, sonra x) çizer. page_schema_patch'te flex kapta gateway index'i görünen sıraya çevirir ve kardeş konumlarını yeniden yazar; ızgara kapta sırayı position.y verir, sona eklemeyen index PAGE-033 uyarısıdır. layout_schema_patch ve component_schema_patch bu çeviriyi YAPMAZ: orada sırayı position.y ile verin) | set_event {target, event, eventValue, replaceBoundFlows?} (olayın BÜTÜN bağlarını bu tek akışa indirir; olayda başka akış bağlıysa gateway PAGE-034 ile reddeder — bilerek değiştirmek için replaceBoundFlows: true) | add_event {target, event, eventValue} (olaya bir akış DAHA bağlar; bağlı akışların hepsi paralel ve birbirinden bağımsız koşar, sıra garantisi yoktur — sıra gerekiyorsa flow-trigger. Aynı akış iki kez 422 duplicate-event. Mobil runtime yalnız İLK akışı koşar) | remove_event {target, event, eventValue?} (olayı diziden çıkarır; target: 'page' sayfa düzeyi olaylar. Olaya birden fazla akış bağlıysa eventValue ile hangisi olduğunu verin, yoksa 422 ambiguous-event. set_event ile eventValue: null olay SİLMEZ, değersiz ölü kayıt bırakır. Olmayan olay 422 missing-event) | set_validations {target, validations: [{type, message, value, isActive}]} (komponentin validation listesini TAMAMEN değiştirir; boş dizi hepsini siler) | set_appearance {target, appearance: {heightType?, widthType?, tooltip?, background?, …}} (kurulu düğümün appearance'ına YALNIZ verilen alanları yazar, gerisini korur; null alanı temizler; bilinmeyen alan unknown-appearance-field ile reddedilir. GÖRÜNÜRLÜK appearance'ta DEĞİLDİR: gizlemek için replace_prop {target, prop: 'isHidden', value: true}, koşul için set_binding {prop: 'isHidden'} — add_component gövdesindeki appearance.isHidden'ı gateway props.isHidden'a taşır) | resize_component {target, size?: {w,h}, appearance?: {widthType,widthString,heightType,heightString}, states?: {isWrapper,isScrollable,isSticky,isDroppable,isFloating,isQuickStack}, breakpoint?: 1024} (gateway adı: platforma set_block {target, breakpoint, size} + set_appearance {target, appearance} + set_states {target, states} olarak gider — o üç op'u doğrudan da gönderebilirsiniz. move_component'e konan size reddedilir. Izgarada blocks[].size, flex'te appearance geçerlidir; ebeveynin düzeni yazılanı etkisiz kılıyorsa PAGE-019 uyarısı çıkar. states bir PROP DEĞİL düğüm alanıdır ve replace_prop onu yazamaz — düğümü silmeden isWrapper bayrağını değiştirmenin yolu budur). set_prop_type {target, prop, propType: 'static'|'code'|'translation', ref?, data?} (replace_prop sarmalıyla aynı sonucu verir ve mevcut data'yı tasarım yedeği olarak taşır. Bu op prop'un TİPİNİ yerinde değiştirir. Taşıyıcı alan tipe göre: static → data, code ve translation → ref (runtime data'yı her turda ezer, anahtar yalnız ref'te yaşar). Eksik taşıyıcı istek gönderilmeden reddedilir; page_schema_validate aynı kararı verir) | reorder {parent ('root' ya da elementId), order: [çocuk elementId'lerinin TAMAMI, yeni sırada]} (yalnız DİZİ sırasını değiştirir; çizim sırası blocks.position'dır — görünen sırayı değiştirmek için konumları set_block/move_component ile de yazın) | set_state_binding {target, binding: {name, expression, targets}} (ada göre ekler/değiştirir) | remove_state_binding {target, name} | batch {gestureId?, label?, ops: [...]} (iç içe batch yok). target = elementId. İstek atomiktir: tek op reddedilirse hiçbiri yazılmaz.");
27
+ export const opsParam = z.array(z.record(z.unknown())).describe("Micro-op listesi. Op sözlüğü: replace_prop {target, prop, value} (value ham değer, code sarmalı {type: 'code', ref: '{{ … }}'} ya da translation sarmalı {type: 'translation', ref: '<çeviri anahtarı>'} — prop tipini değiştirmek için komponenti silip yeniden EKLEMEYİN, ona yazan setter'ların hedefi silinir (FLOW-018); platform replace_prop'ta expression alanını OKUMAZ — gateway onu code sarmalına çevirir) | set_binding {target, prop, expression: '{{ Page.variables.x }}'} | add_component {parent (üst elementId ya da 'root'), index?, component: {elementId, name (= elementId), type, props, blocks: [{breakpoint: 1024, size: {w,h}, position: {x,y}}]}} (blocks ZORUNLU, gateway doldurmaz — blocks'suz ya da null blocks'lu düğüm istek gitmeden PAGE-037 ile reddedilir, platform onu kayıtta 500 NullReferenceException ile düşürürdü; flex kapta position yalnız sıradır; ifadede name okunur, elementId değil) | remove_component {target} | move_component {target, parent, index?, position: {x, y}} (index ÇİZİM sırası değildir, runtime çocukları blocks.position'a göre (y, sonra x) çizer. page_schema_patch'te flex kapta gateway index'i görünen sıraya çevirir ve kardeş konumlarını yeniden yazar; ızgara kapta sırayı position.y verir, sona eklemeyen index PAGE-033 uyarısıdır. layout_schema_patch ve component_schema_patch bu çeviriyi YAPMAZ: orada sırayı position.y ile verin) | set_event {target, event, eventValue, replaceBoundFlows?} (olayın BÜTÜN bağlarını bu tek akışa indirir; olayda başka akış bağlıysa gateway PAGE-034 ile reddeder — bilerek değiştirmek için replaceBoundFlows: true) | add_event {target, event, eventValue} (olaya bir akış DAHA bağlar; bağlı akışların hepsi paralel ve birbirinden bağımsız koşar, sıra garantisi yoktur — sıra gerekiyorsa flow-trigger. Aynı akış iki kez 422 duplicate-event. Mobil runtime yalnız İLK akışı koşar) | remove_event {target, event, eventValue?} (olayı diziden çıkarır; target: 'page' sayfa düzeyi olaylar. Olaya birden fazla akış bağlıysa eventValue ile hangisi olduğunu verin, yoksa 422 ambiguous-event. set_event ile eventValue: null olay SİLMEZ, değersiz ölü kayıt bırakır. Olmayan olay 422 missing-event) | set_validations {target, validations: [{type, message, value, isActive}]} (komponentin validation listesini TAMAMEN değiştirir; boş dizi hepsini siler) | set_appearance {target, appearance: {heightType?, widthType?, tooltip?, background?, …}} (kurulu düğümün appearance'ına YALNIZ verilen alanları yazar, gerisini korur; null alanı temizler; bilinmeyen alan unknown-appearance-field ile reddedilir. GÖRÜNÜRLÜK appearance'ta DEĞİLDİR: gizlemek için replace_prop {target, prop: 'isHidden', value: true}, koşul için set_binding {prop: 'isHidden'} — add_component gövdesindeki appearance.isHidden'ı gateway props.isHidden'a taşır) | resize_component {target, size?: {w,h}, appearance?: {widthType,widthString,heightType,heightString}, states?: {isWrapper,isScrollable,isSticky,isDroppable,isFloating,isQuickStack}, breakpoint?: 1024} (gateway adı: platforma set_block {target, breakpoint, size} + set_appearance {target, appearance} + set_states {target, states} olarak gider — o üç op'u doğrudan da gönderebilirsiniz. move_component'e konan size reddedilir. Izgarada blocks[].size, flex'te appearance geçerlidir, blocks yine zorunlu; ebeveynin düzeni yazılanı etkisiz kılıyorsa PAGE-019 uyarısı çıkar. states bir PROP DEĞİL düğüm alanıdır ve replace_prop onu yazamaz — düğümü silmeden isWrapper bayrağını değiştirmenin yolu budur). set_prop_type {target, prop, propType: 'static'|'code'|'translation', ref?, data?} (replace_prop sarmalıyla aynı sonucu verir ve mevcut data'yı tasarım yedeği olarak taşır. Bu op prop'un TİPİNİ yerinde değiştirir. Taşıyıcı alan tipe göre: static → data, code ve translation → ref (runtime data'yı her turda ezer, anahtar yalnız ref'te yaşar). Eksik taşıyıcı istek gönderilmeden reddedilir; page_schema_validate aynı kararı verir) | reorder {parent ('root' ya da elementId), order: [çocuk elementId'lerinin TAMAMI, yeni sırada]} (yalnız DİZİ sırasını değiştirir; çizim sırası blocks.position'dır — görünen sırayı değiştirmek için konumları set_block/move_component ile de yazın) | set_state_binding {target, binding: {name, expression, targets}} (ada göre ekler/değiştirir) | remove_state_binding {target, name} | batch {gestureId?, label?, ops: [...]} (iç içe batch yok). target = elementId. İstek atomiktir: tek op reddedilirse hiçbiri yazılmaz.");
28
28
  /**
29
29
  * PAGE-008 — `set_event` uydurma bir olay adıyla 200 döner ve bağ SESSİZCE ölü kalır: motor olayı
30
30
  * komponentin yaydığı adla eşleştirir, eşleşme yoksa akış hiç çalışmaz. Kontrol yalnız hedefin
@@ -58,7 +58,8 @@ export function rejectedOpsMessage(unordered) {
58
58
  const validationHint = rejected.some(entry => entry.code === 'PAGE-016')
59
59
  ? " PAGE-016 çalışan biçimi dört alanı da taşır: { type: 'required'|'email'|'regex'|'max-length'|'min-length'|'max-value'|'min-value'|'value-options', message: '<kullanıcıya görünen metin>', value: <eşik/desen/true>, isActive: true }."
60
60
  : '';
61
- return `Ön kontrol ${rejected.length} op'u reddetti; yama atomik olduğu için HİÇBİR op gönderilmedi (istek gönderilmedi). Düşen op'ları düzeltip TÜM listeyi tek çağrıda yeniden gönderin.${validationHint}\n${lines.join('\n')}\nrejectedOps: ${JSON.stringify(rejected)}`;
61
+ const bodyHint = rejected.some(entry => entry.code === 'PAGE-037') ? ` PAGE-037 ${COMPONENT_BODY_RULE}` : '';
62
+ return `Ön kontrol ${rejected.length} op'u reddetti; yama atomik olduğu için HİÇBİR op gönderilmedi (istek gönderilmedi). Düşen op'ları düzeltip TÜM listeyi tek çağrıda yeniden gönderin.${validationHint}${bodyHint}\n${lines.join('\n')}\nrejectedOps: ${JSON.stringify(rejected)}`;
62
63
  }
63
64
  export function collectTypes(nodes, into) {
64
65
  for (const node of nodes ?? []) {
@@ -1,4 +1,4 @@
1
- import { invalidScreenEventNames, invalidWidgetTypeNodes, nestedRepeaterNodes, nestedRepeaterMessage, blockSizeNote, heightTypeNote, sizeStringNote } from '../knowledge/normalize.js';
1
+ import { invalidScreenEventNames, invalidWidgetTypeNodes, nestedRepeaterNodes, nestedRepeaterMessage, assertComponentBlocks, blockSizeNote, heightTypeNote, sizeStringNote } from '../knowledge/normalize.js';
2
2
  import { FUNCTIONS_NAMESPACE } from '../knowledge/functionGuards.js';
3
3
  import { applyCatalogLayoutRules } from './components.js';
4
4
  import { PlatformApiError } from '../platform/errors.js';
@@ -10,15 +10,24 @@ const GUARD_CODES = {
10
10
  /**
11
11
  * JS tanımlayıcısı olmayan ya da AYRILMIŞ `name` alanlarını toplar; ad
12
12
  * `const <ad> = Components["<ad>"]` üretimine girer.
13
+ *
14
+ * Anahtar runtime'daki gibi hesaplanır: `runCode` addan yalnız İLK tireyi siler
15
+ * (`key.replace('-', '')`, `ng-apps-flow/src/flow-execution.functions.ts`). 2026-10-03'e kadar burada
16
+ * bütün tireler siliniyordu (`/-/g`); `a-b-c` denetimden geçti ve sayfadaki bütün ifadeleri
17
+ * düşürdü (Y-15, ölçüldü: `y15-cift-tire`).
13
18
  */
14
19
  export function invalidComponentNames(nodes) {
15
20
  const found = [];
16
21
  for (const node of nodes ?? []) {
17
22
  const name = node.name;
18
- if (typeof name === 'string' && name.length > 0 && !/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name.replace(/-/g, ''))) {
19
- found.push(`'${name}' (${node.elementId ?? '?'})`);
23
+ const key = typeof name === 'string' ? name.replace('-', '') : '';
24
+ if (typeof name === 'string' && name.length > 0 && !/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)) {
25
+ const dashes = key.includes('-')
26
+ ? ` — runtime addan yalnız ilk tireyi siler, kalan '${key}' JS tanımlayıcısı değil; tire yerine _ kullan ('${name.replace(/-/g, '_')}')`
27
+ : '';
28
+ found.push(`'${name}' (${node.elementId ?? '?'})${dashes}`);
20
29
  }
21
- else if (typeof name === 'string' && name.replace(/-/g, '') === FUNCTIONS_NAMESPACE) {
30
+ else if (typeof name === 'string' && key === FUNCTIONS_NAMESPACE) {
22
31
  // K1: preamble her komponent için `const <ad> = Components["<ad>"]` üretir; `Functions` adlı
23
32
  // komponent kullanıcı fonksiyonlarının ad alanını ezer ve sayfadaki her Functions.x() ölür.
24
33
  found.push(`'${name}' (${node.elementId ?? '?'}) — ayrılmış ad: ${FUNCTIONS_NAMESPACE} kullanıcı fonksiyonlarının ad alanıdır (K1)`);
@@ -29,8 +38,8 @@ export function invalidComponentNames(nodes) {
29
38
  }
30
39
  /**
31
40
  * Sayfa ve layout yazma yollarının ORTAK kapısı: olay adı (PAGE-011 · LAY-004), widget düğümü
32
- * (PAGE-012 · LAY-005), komponent adı (PAGE-009) ve iç içe repeater (PAGE-013) denetimleri —
33
- * hepsi istek gitmeden 400.
41
+ * (PAGE-012 · LAY-005), komponent adı (PAGE-009), blocks (PAGE-037) ve iç içe repeater (PAGE-013)
42
+ * denetimleri — hepsi istek gitmeden 400.
34
43
  *
35
44
  * PAGE-009 ve PAGE-013'ün layout ayağı 2026-09-09'da eklendi: ikisi de yalnız sayfa yollarında
36
45
  * koşuyordu, oysa layout `views`'ü aynı runtime'dan geçer — bozuk ad o layout'a bağlı HER
@@ -45,17 +54,28 @@ export function assertScreenWriteGuards(kind, screen) {
45
54
  const badWidgets = invalidWidgetTypeNodes(screen.schema, codes.widgets);
46
55
  if (badWidgets.length > 0)
47
56
  throw new PlatformApiError(`Geçersiz widget düğümü (istek gönderilmedi): ${badWidgets.join(' · ')}`, 400);
48
- const badNames = invalidComponentNames(screen.schema);
49
- if (badNames.length > 0) {
50
- throw new PlatformApiError(`PAGE-009 — komponent adı JS tanımlayıcısı değil ya da ayrılmış (istek gönderilmedi): ${badNames.join(' · ')}. `
51
- + 'Runtime her komponent için `const <ad> = Components["<ad>"]` üretir; boşluklu ya da geçersiz bir ad '
52
- + 'SAYFADAKİ TÜM ifadeleri "Missing initializer in const declaration" ile öldürür (layout adları her sayfaya sızar). '
53
- + `name alanını elementId ile aynı yaz; okunabilir Türkçe başlık için props.title / props.text / props.label kullan (${codes.noun} ${codes.tree}).`, 400);
54
- }
57
+ assertComponentNames(kind, screen.schema);
58
+ assertComponentBlocks(screen.schema);
55
59
  const nested = nestedRepeaterNodes(screen.schema);
56
60
  if (nested.length > 0)
57
61
  throw new PlatformApiError(nestedRepeaterMessage(nested), 400);
58
62
  }
63
+ /**
64
+ * PAGE-009. `page_schema_validate` de bunu çağırır: kuru koşu yazmayla aynı kararı verir — Y-15
65
+ * ölçümünde bozuk ad kuru koşuda `valid:true` dönüyor, yazmada 400 alıyordu.
66
+ */
67
+ export function assertComponentNames(kind, nodes) {
68
+ const codes = GUARD_CODES[kind];
69
+ const badNames = invalidComponentNames(nodes);
70
+ if (badNames.length === 0)
71
+ return;
72
+ throw new PlatformApiError(`PAGE-009 — komponent adı JS tanımlayıcısı değil ya da ayrılmış (istek gönderilmedi): ${badNames.join(' · ')}. `
73
+ + 'Runtime her komponent için `const <ad> = Components["<ad>"]` üretir ve addan yalnız İLK tireyi siler; boşluklu, '
74
+ + 'iki tireli ya da geçersiz bir ad SAYFADAKİ TÜM ifadeleri "Missing initializer in const declaration" ile öldürür '
75
+ + '(layout adları her sayfaya sızar). name alanını elementId ile aynı yaz; ad verilmezse gateway elementId\'yi ad olarak '
76
+ + 'yazar, bu yüzden elementId de geçerli bir tanımlayıcı olmalı (harf, rakam, _). '
77
+ + `Okunabilir Türkçe başlık için props.title / props.text / props.label kullan (${codes.noun} ${codes.tree}).`, 400);
78
+ }
59
79
  /**
60
80
  * Katalog kurallarını (blok sınırı · auto-height · boyut string'i) ağaca uygular ve
61
81
  * `withNormalization` kuyruğuna giren üç notu döndürür. Boş/verilmemiş ağaçta katalog
@@ -4,7 +4,7 @@ import { z } from 'zod';
4
4
  export const hostParam = z.enum(['page', 'layout', 'component'])
5
5
  .describe("Akışın barındırıcısı: page | layout | component (widget de component'tir)");
6
6
  export const schemaParam = z.array(z.record(z.unknown()))
7
- .describe('Komponent ağacı (DesignComponentManagementSchema listesi); verilmezse mevcut şema korunur');
7
+ .describe("Komponent ağacı (DesignComponentManagementSchema listesi): her düğümde elementId, name (= elementId), type, blocks zorunlu — blocks: [{ breakpoint: 1024, size: { w, h }, position: { x, y } }], gateway doldurmaz (PAGE-037). Verilmezse mevcut şema korunur");
8
8
  export const eventsParam = z.array(z.record(z.unknown()))
9
9
  .describe("Sayfa/layout düzeyi olaylar: [{ name, value }] — value bir flow id'sidir");
10
10
  export const modeParam = z.enum(['summary', 'subtree', 'full']).optional().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 şema.");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appaflytech/zeyrek-ai-mcp",
3
- "version": "0.0.32",
3
+ "version": "0.0.34",
4
4
  "type": "module",
5
5
  "description": "Zeyrek (NgApps) platform APIs as MCP tools — data, logic and Studio design surface in one gateway",
6
6
  "license": "UNLICENSED",