@appaflytech/zeyrek-ai-mcp 0.0.40 → 0.0.41

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.
@@ -903,7 +903,7 @@ export const componentKnowledge = {
903
903
  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).",
904
904
  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.',
905
905
  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).',
906
- 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.',
906
+ 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. Biçim: statik değer seçenek anahtarlarının JSON dizisidir ({type:"static", data:["pdf","png"]}) ya da tek virgüllü metin ("pdf,png"); nesne, seçenek nesnesi dizisi ({label,value}), satır sarmalı ve sayı invalid-multiselect-value ile reddedilir (g-23 / b-23; öncesinde 500 veriyordu).',
907
907
  customFiletypes: 'Listede olmayan uzantılar için serbest metin (2026-09-17): heic, dwg;psd gibi — virgül, noktalı virgül ve boşluk ayraçtır, baştaki nokta ve yıldız atılır, büyük/küçük harf önemsizdir. allowedFiletypes ile BİRLEŞİR, onun yerini almaz.',
908
908
  maxFileCount: 'Yalnız isMultiple açıkken okunur; alanda zaten duran dosyalar sayıya dahildir.'
909
909
  }
@@ -922,7 +922,7 @@ export const componentKnowledge = {
922
922
  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).",
923
923
  text: 'Butonun üzerindeki metin (varsayılan "Attach File"); ikon sabittir. Alanın başlığı ayrı bir prop\'tur: label.',
924
924
  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).',
925
- 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. Akış prop\'a düz metin yazabilir ("pdf,png", customFiletypes ile aynı ayraçlar) — 2026-09-25`e kadar bu komponenti `input.map is not a function` ile düşürüyordu (fileInput okuyordu).',
925
+ 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. Akış prop\'a düz metin yazabilir ("pdf,png", customFiletypes ile aynı ayraçlar) — 2026-09-25`e kadar bu komponenti `input.map is not a function` ile düşürüyordu (fileInput okuyordu). Biçim: statik değer seçenek anahtarlarının JSON dizisidir ({type:"static", data:["pdf","png"]}) ya da tek virgüllü metin ("pdf,png"); nesne, seçenek nesnesi dizisi ({label,value}), satır sarmalı ve sayı invalid-multiselect-value ile reddedilir (g-23 / b-23; öncesinde 500 veriyordu).',
926
926
  customFiletypes: 'Listede olmayan uzantılar, serbest metin ("heic, dwg;psd"). Virgül, noktalı virgül ve boşluk ayraçtır; baştaki nokta ve yıldız atılır, büyük/küçük harf fark etmez. allowedFiletypes ile BİRLEŞİR, onun yerine geçmez (fileInput ile aynı ad ve aynı kural). Çok parçalı uzantı da yazılabilir ("tar.gz"): eşleşme adın SONUNA bakar, son noktadan sonrasına değil.',
927
927
  maxFileSizeMb: 'Dosya BAŞINA en büyük boyut (MB). Boş/0/negatif değer sınırsız demektir; aşan dosya değere girmez, uyarı komponentin içinde çizilir.',
928
928
  maxFileCount: "Çoklu kipte toplam dosya sayısı sınırı — zaten seçili olanlar da sayılır. Boş/0/negatif sınırsız. Tekli kipte hiç okunmaz ve panelde de gizlidir (isMultiple'a bağlı).",
@@ -2655,6 +2655,46 @@ export const componentKnowledge = {
2655
2655
  showRemind: 'Hatırlat düğmesi; varsayılan true.'
2656
2656
  }
2657
2657
  },
2658
+ 'version-compare': {
2659
+ summary: 'Sürüm karşılaştırma çekmecesi (ADR 0018): iki sürümün dosyasını TARAYICIDA okuyup farkını gösterir — sürüm kartları + seçici + yer değiştir, bağlam notu, İçerik sekmesi (türe göre görünüm: belge kâğıdı, pdf sayfaları, txt satırları, xlsx/csv ızgarası, pptx slaytları, tür değişmiş, karşılaştırılamaz görüntüleyici; değişiklik listesi, tür süzgeci, sayaç, Alt+↓/↑) ve Alanlar ve ayarlar sekmesi; mode: inline okuma modudur: işaretli kâğıt, "İşaretli / Standart görünüm" anahtarı ve gezinme, aynı satırda uygulamanın araçları (inlineTools) ve kâğıdın yakınlaştırması (inlineZoom). Ölçüler her temada tasarımın px değeridir (temanın spacing ayarını izlemez). Veriyi KENDİSİ çekmez: sürüm listesi, çift, dosyalar, alanlar, bağlam notu ve metinler prop’tan gelir; hangi sürümün görüneceği ve hak kuralı uygulamanındır.',
2660
+ behavior: [
2661
+ "Dosya yolu: files[versionId] = { name, type, url | data, size?, error?, missing? }. Seçili çiftte bir sürümün dosyası yoksa komponent fileRequest { id } yayar (kimlik başına bir kez; dosya gelip sonra files'tan düşerse yeniden; editörde yayılmaz); akış dosyayı uçtan alıp files'a yazar. data verilirse (data URI ya da çıplak base64) dosya gelmiş sayılır; url verilirse komponent dosyayı KENDİSİ çeker (indirme ilerlemesi gösterilmez, 'Dosyalar alınıyor' yalnız aşamadır; iki dosya gelince toplam boyut yazılır). Akış dosyayı alamadıysa files[id].error = { message, at } yazar (at: deneme zamanı) → 'Dosya alınamadı' kartı; Tekrar dene fileRequest'i yeniden yayar (url'de yeniden çeker) ve files[id]'nin İÇERİĞİ tekrar denemeden öncekinden farklı olana dek bekleme kartını gösterir — platform bağlı prop'u her yazımda yeni nesneyle verdiği için karşılaştırma içerikle yapılır; hata kaydı her denemede ayırt edilebilir olmalı (aynı { error: true } yeniden yazılırsa kart beklemede kalır).",
2662
+ "Okuma ve fark Web Worker'da koşar (okuyucular: docx, txt, xlsx, csv, pptx, pdf; png/jpg/jpeg, metin katmanı olmayan (taranmış) pdf ve okuyucusu olmayan tür karşılaştırılamaz): iki aşamalı yükleme 'Dosyalar alınıyor' → 'Karşılaştırılıyor' (bölüm sayacı), sayfa donmaz; bitince summary { all, add, del, mod } yayar. Okunamayan dosyada (çözülemeyen data dahil) error { id, kind: read, message } yayar ve 'Dosya okunamadı' kartı çıkar; karşılaştırılamaz türde error { id, kind: unsupported, message: <tür> }, summary yok, iki dosya yan yana görüntüleyicide. Akışın yazdığı hata kaydında error { id, kind: fetch, message: error.message }; komponent url'yi çekemezse de error { id, kind: fetch, message: HTTP durumu ya da çekme hatası }. Worker yüklenemez, çökerse ya da karşılaştırmanın kendisi hata verirse (ör. çok büyük dosya) error { kind: read, message } (id yok) ve okunamadı kartı — kart sonsuz beklemez.",
2663
+ "Durum kartları (İçerik sekmesi): dosya alınamadı (Tekrar dene); okunamadı ('<ad> açılamıyor: dosya parola korumalı ya da bozuk…' — aynı bozuk sürüme dönünce de adıyla; iki sürüm de canOpen ise 'Dosyaları yan yana aç' — iki dosya bantsız görüntüleyicide, okunabilen taraf işaretsiz kendi görünümünde, verisi (data) çözülemeyen tarafın karşısı da; odak görüntüleyiciye); fark yok (kart DEĞİL: belge hazırdaki gibi işaretsiz çizilir, üstünde yeşil bant 'Bu iki versiyonun metni aynı', alan farkı varsa sayısı ve adlarıyla 'Alanlar ve ayarlara geç' — inline'da alan cümlesi ve düğmesi yok; boş değişiklik listesi çizilmez, slayt listesi kalır; sekme sayısı 0); dosya yok (files[id].missing → istenmez, \"Versiyon 1'de dosya yok\" + sözlüğün noFileText'i, ezilebilir; çekmecede ardından noFileFields 'Alanlar ve ayarlar karşılaştırılabilir.', inline'da yok); hak yok (SEÇİLİ sürüm disabled → dosyası istenmez, kartında kilit + disabledReason, 'Bu versiyonu aç' yok, '<etiket> ile karşılaştırma hakkınız yok' + texts.permissionNote). Sıra: hak yok → dosya yok → alınamadı → okunamadı; missing aynı sürümde disabled'ı geçer.",
2664
+ "Çok sayıda değişiklik (100+, bütün görünümlerde: belge, pdf, txt, xlsx/csv, pptx, tür değişmiş): belge, pdf sayfası ve tür değişmişte bölüm bölüm (bant '312 değişiklik bulundu. Bölümler sırayla çiziliyor (14 / 46)…'), txt ve ızgarada 40'ar satır ('Satırlar sırayla çiziliyor (400 / 20000)…') animasyon karelerinde kademeli çizilir, kalanı iskelet; pptx'te slayt bir seferde çizilir, yalnız slayt listesi kaydırdıkça yüklenir; görünüm anahtarı yok (birleşiği olan türde birleşik, txt yan yana). Liste '46 bölümde 312 değişiklik · liste kaydırdıkça yüklenir' (ızgarada 'n sayfada', slaytta 'n slaytta', txt'de yalnız sayı; 60'ar öğe, slayt listesi de); seçilen değişiklik (liste, Alt+↓/↑, selected) çizilmemişse ona dek çizilir/yüklenir. Sayaç tek satır.",
2665
+ "Ekran genişliği (viewport): 1200–1439 px'te sol liste 240 px; 1200 px altında birleşik varsayılan (yan yana seçilebilir), liste gizli, 'Değişiklikler <sayı>' düğmesi listeyi soldan açılan panelde açar (seçince ve Esc ile kapanır). Çekmecenin kendi genişliği (1440'tan 1200 px, 1200–1439'da ekran − 72 px, altında tam) barındıran çerçevenindir; komponent düğümünü doldurur.",
2666
+ "mode: inline (okuma modu): başlık, sürüm şeridi, sekmeler, liste ve alt bilgi yok, dialog değildir (açılışta odak almaz, Esc kapatmaz). Üstte bölüm menüsü ('Bölüm: <ad> ▾', değişikliği olan bölümler; seçilince bölümün ilk değişikliği; hiçbir bölümün adı yoksa — başlıksız belge — menü çizilmez) + önceki / 'n / N' / sonraki; altında \"<eski etiket>'e göre işaretli · yalnız metin farkları\" + lejant ve bütün belge tek birleşik 680 px kâğıtta (daralma yok). Öbür türler aynı etiketle kendi görünümünde: birleşiği olan türde birleşik (pdf sayfaları, ızgara, slayt, tür değişmiş), txt iki satır sütunu (yalnız yan yana); karşılaştırılamaz sonuçta iki dosya yan yana görüntüleyicide, işaret satırı ve lejant yok (tasarım 21). Üst satırın başında 'İşaretli görünüm / Standart görünüm' anahtarı (tasarım B 7, gezinmeyle aynı satır): inlineView ('marked' | 'standard') prop'u açık olanı söyler, tıklayınca inlineViewChange { inlineView } (setter) yayılır; 'standard'da bileşen yalnız anahtar satırını çizer (kâğıt gizli, durumu korunur; gizliyken seçilen değişikliğe kâğıt yeniden görününce kayar), standart görünümün kendisi (PDF görüntüleyici) ve görev çubuğu sayfanındır. Satır yükleme ve durum kartlarında da durur. Karşılaştırılamaz bandında 'Alanlar ve ayarlar karşılaştırması kullanılabilir.' cümlesi inline'da yok (Alanlar sekmesi yok). Gezinme selected / selectionChange / summary ile.",
2667
+ "inline satırında uygulamanın araçları (tasarım B 7, ADR 0019): inlineTools = [{ key, kind: button | text | separator, text, icon: { name }, isDisabled, place: end | start }]. place end (varsayılan) satırın sağında, iki görünümde, dizinin sırasıyla 4 px arayla; place start anahtarın yanında (14 px) ve YALNIZ standart görünümde (işaretli görünümde orada bölüm menüsü ve sayaç durur — B 7 'Sayfa x / n'). button 28 px simge düğme (16 px simge, ikincil renk), text erişilebilir adı ve ipucu, düğmede yazı yok; text 12,5 px ikincil metin, sağ grupta en az 44 px ve ortalı ('100%'); separator 1 × 20 px çizgi, iki yanında 6 px. Basılan düğme inlineToolClick { key } yayar (setter yok); isDisabled düğme basılmaz, yaymaz. Bileşen öğenin anlamını bilmez: sayfa numarası, yüzde, indir / yazdır / tam ekran akışın işidir. Akış diziyi bozuk yazarsa (null, tek nesne, metin öğe) satır düşmez, kayıt olmayan öğe atlanır; bilinmeyen kind button, bilinmeyen place end sayılır. Satır yükleme ve durum kartlarında da araçları çizer.",
2668
+ "inline satırında menü öğesi ve taşma menüsü (ADR 0020, qm S-30 / S-24): kind menu = bölüm menüsü görünümünde kenarlı 30 px düğme (text + ▾), açılan liste sağa yaslı, başlığı title, satırları options [{ key, text, isDisabled }]; seçenek seçilince inlineToolClick { key: <öğe>, item: <seçenek> }; seçeneksiz menu pasif. showText: true olan button simgenin yanında text'i yazar (ör. 'Düzenle'). Sağ grubun TAM genişliği (gizli ölçü kopyası) satırın iç genişliğine sol grup + 12 px aralıkla sığmıyorsa menu öğeleri satırda kalır, bütün button'lar (yazılı olanlar dahil) sırayla tek '⋯' düğmesinin (28 px, ad 'Diğer araçlar' / 'More tools') listesine girer: simge + menuText (yoksa text); text ve separator listede yok; pasif düğme listede pasif; seçilen satır satırdaki düğmeyle AYNI inlineToolClick { key }'i yayar — uygulamanın akışı değişmez. Satır, sol grup ya da sağ grup boyu değişince (ResizeObserver) yeniden karar verilir. Liste Esc ve dışarı tıklamayla kapanır, Esc'te odak düğmeye döner.",
2669
+ "inlineZoom (yüzde, varsayılan 100): inline kipte işaretli görünümün çizimi o ölçekle çizilir (CSS zoom; %100 = 680 px kâğıt, 150 = 1020 px); yerleşim ve kaydırma büyür, alana sığmayan kâğıt sola yaslanıp yatay kaydırılır. Etiket satırı ve lejant ölçeklenmez; karşılaştırılamaz görüntüleyicinin (21) kendi yakınlaştırması kalır (inlineZoom ona uygulanmaz). Boş / 0 / eksi / sayı değil → 100; 10–500'e çekilir. Adım ve uygulamanın sınırları akıştadır: ör. − / + düğmeleri inlineToolClick'te değeri hesaplayıp inlineZoom'a ve kendi PDF görüntüleyicisine yazar, metin öğesini ('125%') günceller.",
2670
+ "Yan yana kâğıtlar blok hizalıdır: her blok çifti (tabloda her satır) aynı satırda başlar; bir tarafta olmayan bloğun kesik çizgili yer tutucusu karşı taraftaki blokların satırlarını kaplar. Listeden seçince iki kâğıt o bloğa kayar. Belge her zaman bütün bölümleriyle çizilir: 'Yalnız değişen bölümler' anahtarı ve 'n bölüm değişmedi · Göster' daralması kullanıcı kararıyla kaldırıldı (2026-10-09, qm S-32; onlyChanged prop'u yok). Yalnız eski tarafta olan blok, yeni belgede içinde durduğu başlığın bölüm adını alır (başlıksız txt tarafı docx bölümünü bölmez, S-6). Değişiklik sınırı 5000 (S-8): değişmiş olarak eşleştirilecek öğe ya da sonuçtaki değişiklik sayısı 5000'i aşarsa karşılaştırma gösterilmez, 'Çok fazla değişiklik olduğu için karşılaştırma gösterilemiyor' kartı çıkar ve error { kind: 'tooMany' } yayılır (summary yok). Sürüm seçici bileşenin alt kenarına kadar uzar ve kendi içinde kayar (S-33). Esc dar ekran panelini yalnız görünüyorsa kapatır (S-7). Karşılaştırılamaz görüntüleyicinin pdf.js'i sayfanın globalThis'ine bir şey bırakmaz (S-34).",
2671
+ "pairChange setter'dır: seçiciden seçme ya da yer değiştirme oldId/newId'yi self'e yazar ve event.data = { oldId, newId } ile akışı çalıştırır; akış yeni çiftin files/fields/contextNote'unu yeniler. Kimlikler versions'taki tipiyle (sayı ya da metin) gider. Karşı tarafta seçili ve disabled sürüm seçilemez.",
2672
+ "selectionChange setter'dır: listeden seçme, önceki/sonraki, bölüm menüsü, Alt+↓/↑ ve değişikliği olan sayfa sekmesi (ızgara, pdf) ya da slayt seçimi (ilk değişikliği seçilir) selected'ı self'e yazar; akışa event.data = { selected (n), section, kind: add | del | mod } gider (prop'a yalnız selected yazılır). selected boş ya da listede yoksa ilk değişiklik seçilir. Sayaç tür süzgecine bağlıdır ('n / N'), sonda başa döner. selected dışarıdan (akış) değişince liste ve kâğıt o değişikliğe kayar; ancak komponent kendi seçimini setter'la yazdıktan sonra state'e bağlı selected'ın sonraki akış yazımları prop'a ulaşmıyor (platform davranışı, zeyrek-frontend docs/issues.md ARC-258). selectionChange kendi gezinme dışında da yayılır, SETTER'SIZ (prop'a yazmaz, yük aynı): karşılaştırma hazır olup ilk seçim yapılınca (açılış, çift değişimi — yeni sonuçta aynı n de yeni seçimdir) ve selected dışarıdan yazılıp seçim gerçekten değişince; aynı sonuçta aynı seçim yeniden bildirilmez, akış event.data.selected'ı state'e geri yazınca döngü olmaz. Okuma modunda görev kartının 'Seçili: <bölüm> · <tür>' satırı açılışta bundan dolar (tasarım B 7).",
2673
+ "Alanlar sekmesi dosya beklemeden açılır; durumu (Değişti / Eklendi / Kaldırıldı / Aynı) komponent HESAPLAR: gruplar grup adıyla, alanlar key ile eşlenir; boş → dolu Eklendi, dolu → boş Kaldırıldı; dizi değer öğe öğe (açılan öğe altı çizili yeşil + '· açıldı', kapanan üstü çizili turuncu). 'Yalnız değişenleri göster' varsayılan açık.",
2674
+ "Esc önce açık sürüm seçicisini, sonra açık bölüm menüsünü / liste panelini, sonra çekmeceyi kapatır (close yayar; Vazgeç de close; inline'da kapatmaz). Bileşenin kendi denetimi tıklanınca kalkarsa odak bileşende kalır (seçiciden seçince seçici düğmesi, bölüm menüsünden seçince menü düğmesi, liste panelinden seçince 'Değişiklikler' düğmesi, karttan Alanlar'a geçince Alanlar sekmesi, 'Dosyaları yan yana aç'ta görüntüleyici; öbür kartlarda çekmecenin kökü, inline'da görünüm anahtarı) — Alt+↓/↑ ve Esc çalışmaya devam eder. Açılışta odak başlığa gider, komponent kaldırılınca açan öğeye döner (inline'da ikisi de yok). 'Bu versiyonu aç' yalnız Eski kartta ve canOpen:true sürümde görünür → openVersion { id }.",
2675
+ "mode: drawer (tam görünüm) | inline (okuma modu kâğıdı); inlineView (marked | standard) yalnız inline'da, durumu uygulama tutar (inlineViewChange). view (side | unified) başlangıç değeridir, kullanıcı çekmecede değiştirir (olay yayılmaz); dar ekranda başlangıç birleşiktir.",
2676
+ "xlsx ↔ csv: csv tek sayfasıyla, karşı taraf çok sayfalıysa en benzer sayfayla eşlenir; öbür sayfalar sayfa sekmelerinde 'Karşılaştırılmadı' durumuyla görünür (değişiklik sayısına girmez, satırları işaretsiz), bant 'n sayfa karşılaştırılmadı.' der; csv tarafında adsız eşlenen sayfanın sekmesi listedeki grubun adıyla ('Liste'). Karşılaştırılmayan sayfa yoksa csv'de sekme yok.",
2677
+ "docx ↔ docx'te biçim farkı da 'Değişti' sayılır (f-10): metni aynı, kalınlığı / italiği / altı ya da üstü çizgisi / yazı rengi / vurgu rengi farklı paragraf, başlık, madde ya da tablo satırı mor çizgi + 'Değişti' alır, sözcük işareti yok, neyin değiştiği yazılmaz. Yazı tipi, punto ve stil tanımından gelen biçim sayılmaz; tür değişmişte, pdf ve pptx'te biçim karşılaştırılmaz.",
2678
+ "Dosya türü değişmişse (ör. docx → pdf, xlsx → csv; iki taraftan da metin çıkıyorsa) yalnız birleşik görünüm ve bilgi bandı: 'Dosya türü değişti: <eski etiket> <tür> → <yeni etiket> <tür>.' + 'İki dosyanın da metni çıkarıldı ve metin karşılaştırıldı.' (bir taraf pdf ise 'Bölüm başlıkları pdf'te sayfa içinden okunur; gezinme sayfa ve bölüm bazlıdır.') + 'Biçim ve sayfa düzeni farkı işaretlenmez.' + 'Belge formatı değiştiği için tam karşılaştırma yapılamıyor.'; çok sayfalı xlsx ↔ csv'de ardından 'n sayfa karşılaştırılmadı.'. Excel başka türe dönünce sayfa adı başlık sayılmaz (format kaynaklı).",
2679
+ "Metinler: arayüz metinleri uygulama dilinde (tr, en; diğer diller İngilizce) gelir; texts aynı anahtarlarla bunları ezer (ör. noFileText, lockedReason) ve uygulamaya özgü metinleri taşır: footer (alt bilgi sınır metni — verilirse her türde türün kendi metninin yerine geçer), pickerNote (seçicinin alt notu), fieldsNote (alanlar notu), permissionNote (hak yok kartının açıklaması), groups (seçici grup anahtarı → başlık, ör. { archive: 'Arşiv' })."
2680
+ ],
2681
+ propNotes: {
2682
+ header: '{ code, title, type, ext } — başlığın altındaki rozet satırı (kod mono, ad, tür, dosya türü çipi).',
2683
+ versions: "[{ id, label, date, statusText, tone: ok | warn | info | mute | err, meta, note, group, disabled, disabledReason, canOpen }] — uygulamanın sürüm listesi (fazla alanlar yok sayılır). group boş ya da 'main' ana liste; diğer gruplar seçicide ayrı başlıkla (texts.groups) ve kesik çerçeveli rozetle. disabled sürüm pasif, nedeni disabledReason; SEÇİLİ sürüm disabled ise o sürümle karşılaştırma hakkı yoktur (kilit, hak yok kartı).",
2684
+ oldId: 'Eski taraftaki sürümün id’si; pairChange setter hedefi.',
2685
+ newId: 'Yeni taraftaki sürümün id’si; pairChange setter hedefi.',
2686
+ files: "{ [versionId]: { name, type: uzantı (docx, txt, xlsx, csv, pptx, pdf, png…), url | data, size (bayt), error ({ message, at } — her denemede ayırt edilebilir), missing (true: sürümün dosyası yok, istenmez) } } — yalnız seçili iki sürümün dosyası gerekir.",
2687
+ fields: "{ old: [{ group, items: [{ key, label, value }] }], new: [...] } — value metin ya da metin dizisi, boşsa null; durum komponentte hesaplanır.",
2688
+ contextNote: "{ title, rows: [{ label, value }] } — yeni sürümün bağlam notu (başlık ve etiket/değer satırları).",
2689
+ texts: "Arayüz metni ezmeleri (anahtar → metin, '{ad}' yer tutuculu) + footer, pickerNote, fieldsNote, permissionNote, groups.",
2690
+ mode: 'drawer — tam görünüm; inline — okuma modu: yalnız işaretli kâğıt + görünüm anahtarı, bölüm menüsü ve gezinme.',
2691
+ inlineView: "marked (varsayılan) | standard — inline kipte açık görünüm; inlineViewChange setter hedefi. standard'da bileşen yalnız anahtar satırını çizer, sayfa kendi standart görünümünü gösterir. State'e bağlıysa inlineViewChange akışı state'i de yazmalı (oldId/newId gibi).",
2692
+ inlineTools: "[{ key, kind: button | text | separator | menu, text, icon: { name }, isDisabled, place: end | start, showText, menuText, title, options: [{ key, text, isDisabled }] }] — inline satırında uygulamanın araçları; button'a basılınca inlineToolClick { key }, menu seçeneğinde inlineToolClick { key, item }. place start yalnız standart görünümde, anahtarın yanında. Sağ grup sığmayınca menu dışındaki düğmeler '⋯' listesine girer (ADR 0020).",
2693
+ inlineZoom: 'Yüzde, varsayılan 100 (10–500) — inline kipte işaretli görünümün ölçeği; adım ve sınırları akış belirler.',
2694
+ view: 'side | unified — başlangıç görünümü.',
2695
+ selected: 'Seçili değişikliğin n’si (1’den); selectionChange setter hedefi. Akış yazınca liste ve kâğıt o değişikliğe kayar.'
2696
+ }
2697
+ },
2658
2698
  'process-timeline': {
2659
2699
  summary: 'Süreç örneğinin defteri (kim, ne, hangi karar, yorum) ardından sıradaki ve gelecek adımlar; bölge yeniden başlatma turları ve geçersizleşen görevler işaretli.',
2660
2700
  guide: 'bpm',
@@ -2950,8 +2990,8 @@ export function knowledgeFor(slug) {
2950
2990
  * `switcher` (78/78) prop'larının HEPSİ seçenek taşır ve seçeneklerin hepsi string'dir. `number`
2951
2991
  * tipinde ise `options` bir ENUM DEĞİL sınır çiftidir (`opacity` → [0, 100], `transitionMs` →
2952
2992
  * [0, 1000]) ve `component` tipinde izinli komponent tipi filtresidir — ikisi de denetlenmez.
2953
- * `multiselect` kapalı bir küme taşır ama değeri dizidir; bilerek dışarıda bırakıldı (gateway'in
2954
- * eksik denetimi platformun kendi duvarına düşer, fazla denetimi geçerli yazmayı bloklardı).
2993
+ * `multiselect` kapalı bir küme taşır ama değeri dizidir; DEĞER denetimine girmez (serbest değer —
2994
+ * `"image/*"` — platformda da geçer), yalnız BİÇİMİ denetlenir: `multiselect` haritası (g-23).
2955
2995
  */
2956
2996
  const ENUM_PROP_TYPES = new Set(['dropdown', 'radio', 'switcher']);
2957
2997
  /**
@@ -2967,17 +3007,22 @@ export function catalogTypeRules(components) {
2967
3007
  if (slug === undefined || !Array.isArray(declared))
2968
3008
  continue;
2969
3009
  const props = new Map();
3010
+ const multiselect = new Map();
2970
3011
  for (const entry of declared) {
2971
3012
  const name = typeof entry?.name === 'string' ? entry.name : undefined;
2972
3013
  if (name === undefined || name === '')
2973
3014
  continue;
2974
- const options = ENUM_PROP_TYPES.has(String(entry.type ?? '')) && Array.isArray(entry.options)
3015
+ const type = String(entry.type ?? '');
3016
+ const declaredOptions = Array.isArray(entry.options)
2975
3017
  ? entry.options.map(option => option?.value).filter((value) => typeof value === 'string')
2976
- : undefined;
3018
+ : [];
3019
+ const options = ENUM_PROP_TYPES.has(type) ? declaredOptions : undefined;
2977
3020
  props.set(name, options !== undefined && options.length > 0 ? options : undefined);
3021
+ if (type === 'multiselect')
3022
+ multiselect.set(name, declaredOptions);
2978
3023
  }
2979
3024
  if (props.size > 0)
2980
- rules.set(slug, { props });
3025
+ rules.set(slug, { props, multiselect });
2981
3026
  }
2982
3027
  return rules;
2983
3028
  }
@@ -3032,6 +3077,22 @@ export function schemaCatalogErrors(nodes, rules) {
3032
3077
  }
3033
3078
  const options = typeRules.props.get(name);
3034
3079
  const value = staticPropValue(raw);
3080
+ const multiselectOptions = typeRules.multiselect.get(name);
3081
+ if (multiselectOptions !== undefined) {
3082
+ const wellFormed = value === undefined || value === null || typeof value === 'string'
3083
+ || (Array.isArray(value) && value.every(item => typeof item === 'string'));
3084
+ if (!wellFormed) {
3085
+ const example = JSON.stringify(multiselectOptions.length > 0 ? multiselectOptions.slice(0, 2) : ['pdf', 'docx']);
3086
+ errors.push({
3087
+ componentId,
3088
+ path: `props.${name}`,
3089
+ rule: 'invalid-multiselect-value',
3090
+ message: `'${name}' seçenek değerlerinin JSON dizisini (örn. ${example}) ya da tek virgüllü metni alır; gelen ${JSON.stringify(value)} ikisi de değil.`,
3091
+ suggestions: [...multiselectOptions]
3092
+ });
3093
+ }
3094
+ continue;
3095
+ }
3035
3096
  if (options === undefined || value === undefined || value === null || value === '')
3036
3097
  continue;
3037
3098
  if (options.includes(String(value)))
@@ -798,6 +798,39 @@ export const componentEventContracts = {
798
798
  reads: "self'e yazılmaz — event.data ile oku; okumayanlara bildirim gönderen akışı çağır"
799
799
  }
800
800
  },
801
+ 'version-compare': {
802
+ mount: NO_DATA,
803
+ pairChange: {
804
+ payload: { oldId: 'string | number', newId: 'string | number' },
805
+ reads: "setter: oldId/newId self'e yazılır; event.data = { oldId, newId } — akış yeni çiftin files/fields/contextNote'unu yeniler"
806
+ },
807
+ fileRequest: {
808
+ payload: { id: 'string | number (dosyası gereken sürüm)' },
809
+ reads: "self'e yazılmaz — event.data.id ile oku; akış dosyayı alıp files[id] = { name, type, data, size } yazar, alamazsa files[id].error = { message, at } yazar (at: deneme zamanı). Tekrar dene fileRequest'i yeniden yayar ve files[id]'nin İÇERİĞİ öncekinden farklı olana dek bekler — hata kaydı her denemede ayırt edilebilir olmalı (aynı { error: true } yeniden yazılırsa kart beklemede kalır). Dosya gelip sonra files'tan düşerse yeniden istenir"
810
+ },
811
+ openVersion: { payload: { id: 'string | number' }, reads: "self'e yazılmaz — event.data.id ile oku" },
812
+ summary: {
813
+ payload: { all: 'number', add: 'number', del: 'number', mod: 'number' },
814
+ reads: "self'e yazılmaz — event.data ile oku; karşılaştırma bitince bir kez — karşılaştırılamaz sonuçta (görsel, taranmış pdf) yayılmaz"
815
+ },
816
+ selectionChange: {
817
+ payload: { selected: 'number (değişikliğin n’si)', section: 'string (değişikliğin bölümü / grubu)', kind: "'add' | 'del' | 'mod'" },
818
+ reads: "kendi gezinmede setter: yalnız selected self'e yazılır; event.data = { selected, section, kind } — okuma modunda görev çubuğunun 'Seçili: <bölüm> · <tür>' satırı. Açılışta / çift değişince (ilk seçim) ve selected dışarıdan yazılıp seçim değişince de yayılır, setter'sız (self'e yazılmaz); aynı seçim yeniden yayılmaz — akış selected'ı geri yazınca döngü olmaz"
819
+ },
820
+ inlineViewChange: {
821
+ payload: { inlineView: "'marked' | 'standard'" },
822
+ reads: "setter: inlineView self'e yazılır; event.data = { inlineView } — inline kipte 'İşaretli / Standart görünüm' anahtarı; sayfa standard'da kendi görüntüleyicisini gösterir, durumu (state) akış tutar"
823
+ },
824
+ inlineToolClick: {
825
+ payload: { key: 'string (basılan inlineTools düğmesinin ya da seçeneği seçilen menu öğesinin key’i)', item: 'string? (yalnız menu öğesinde: seçilen options[].key)' },
826
+ reads: "self'e yazılmaz — event.data.key (menu öğesinde ayrıca event.data.item) ile oku; inline satırındaki uygulama düğmesi (ör. yakınlaştır, indir, yazdır, tam ekran) ya da menu öğesinin seçeneği (ör. versiyon). Sağ grup sığmayınca '⋯' listesinden seçilen düğme satırdakiyle AYNI yükü yayar (ADR 0020). isDisabled düğme / seçenek yaymaz; text / separator öğesi basılmaz"
827
+ },
828
+ close: { payload: {}, reads: 'yük yok — kapat düğmesi, Esc ya da yüklemede Vazgeç' },
829
+ error: {
830
+ payload: { id: 'string | number? (karşılaştırma worker’ı çökerse ya da karşılaştırmanın kendisi hata verirse yok)', kind: "'fetch' | 'read' | 'unsupported' | 'tooMany'", message: 'string? (fetch: files[id].error.message ya da HTTP durumu; read: çözme/okuma hatası; unsupported: dosya türü; tooMany: değişiklik sayısı 5000 sınırını aştı — kimliksiz, kart gösterilir)' },
831
+ reads: "self'e yazılmaz — event.data ile oku"
832
+ }
833
+ },
801
834
  'case-view': {
802
835
  mount: NO_DATA,
803
836
  action: {
@@ -160,7 +160,7 @@ export const mobileComponentKnowledge = {
160
160
  differences: [
161
161
  '`isIndeterminate` `value`yu bastırır ve tire (`remove` glifi) çizer; belirsiz kutuya dokunmak onu seçili yapar.',
162
162
  'Etikete dokunmak da kutuyu çevirir (basış satırın `Pressable`ında toplanır).',
163
- '`page_screenshot` kutuyu `<elementId>__press` ile çevirir (düğmedeki gibi; kök kimliğe `click` kabuğa gider ve kutuyu ÇEVİRMEZ).',
163
+ '`page_screenshot` kutuyu `<elementId>__press` yüzeyinden çevirir (düğmedeki gibi); kök kimliğe `click` de o yüzeye basar (SCR-015).',
164
164
  '`labelPosition` yalnız `left`/`right`; `left` seçilince etiket ile kutu satırın iki ucuna yaslanır.',
165
165
  'Düşenler: `autofocus` ve `focus`/`blur` olayları (onay kutusunun klavye odağı yok, tek sinyal değer değişimidir).'
166
166
  ]
@@ -168,7 +168,7 @@ export const mobileComponentKnowledge = {
168
168
  switch: {
169
169
  summary: 'Açma/kapama anahtarı; shadcn `Switch` ilkeli (`@rn-primitives/switch`) — üç barındırıcıda AYNI görünüm.',
170
170
  differences: [
171
- '`page_screenshot` anahtarı `<elementId>__press` ile çevirir (kök kimliğe `click` kabuğa gider).',
171
+ '`page_screenshot` anahtarı `<elementId>__press` (`role="switch"`) yüzeyinden çevirir; kök kimliğe `click` de o yüzeye basar (SCR-015).',
172
172
  'Platformun kendi anahtarı (iOS/Android `Switch`) KULLANILMAZ: açıkken zemin `--primary`, kapalıyken `--input`, baş `--background`; kanvas, önizleme ve cihaz aynı bileşeni çizer.',
173
173
  '`isReadonly` ve `isDisabled` anahtarı kilitler ve soldurur; `isDisabled` etiketi de soldurur. Tasarım kanvasında anahtar basılamaz olduğu için HER anahtar soluk çizilir — önizlemede ve cihazda değil.',
174
174
  'Düşenler: `autofocus` ve `focus`/`blur` olayları (anahtarın klavye odağı yok, tek sinyal değer değişimidir).'
@@ -179,7 +179,7 @@ export const mobileComponentKnowledge = {
179
179
  differences: [
180
180
  '`presentation` (mobile özgü): `auto` (varsayılan) sekiz ve daha az seçenekli, aramasız listeyi alanın hemen ALTINDA açar (altta yer yoksa ya da klavye açıksa üstte), öbürünü ekranın altından açılan sayfada; `sheet` hep sayfa, `inline` hep alan altı (arama varsa sayfa).',
181
181
  'Sayfada dokuz ve daha çok seçenek ya da `search` akışa bağlıyken tepede arama kutusu durur (iç rol `<elementId>__search`): bağlıysa `search { searchText }` yayar ve listeyi akış yazar, değilse yerelde süzer. Açılış `focus`, kapanış `blur` yayar.',
182
- 'Seçenek görseli web `SelectItem` önceliğiyle: `prefixImage` > `prefixText` (zemini `color`) > `icon` (rengi `color`); seçiliyken kutuda da çizilir. Gizli seçenek (`isHidden`) çizilmez. `isViewmode` kutuyu kilitler, ok ve temizleme düğmesini kaldırır. Düşenler: `options[].tooltip`, `isExpanded`, `isSelectable`, `lookupMode` (ağaç, üzerine gelme ve ↗ lookup penceresi yok).',
182
+ 'Seçenek görseli web `SelectItem` önceliğiyle: `prefixImage` > `prefixText` (zemini `color`) > `icon` (rengi `color`); seçiliyken kutuda da çizilir. Gizli seçenek (`isHidden`) çizilmez. `isViewmode` kutuyu kilitler, ok ve temizleme düğmesini kaldırır. Düşenler: `options[].tooltip`, `isExpanded`, `isSelectable` (ağaç ve üzerine gelme yok). Mobil şema `lookupMode`’u web sınıfından devralır ama mobil çizici onu OKUMAZ: ↗ lookup penceresi açılmaz, yazılan değer etkisizdir.',
183
183
  'Tetikleyici shadcn `Input` görünümündedir (`--input` kenarı; yükseklik, köşe ve punto öteki alanlarla aynı `--ng-form-*` ölçüsü); liste `--popover` yüzeyinde, seçili satırda `check` glifi. Tetikleyici `role="combobox"` taşır (iç rol `<elementId>__trigger`, temizleme düğmesi `<elementId>__clear`), seçilebilir satırlar `role="option"` + `data-value`; liste katmanda açıldığı için `page_screenshot` `select` seçeneği belgede arar ve değerle ya da görünen metinle seçer.',
184
184
  'Seçenekler normalize edilir: düz metin dizisi, nesne dizisi ve `optionType: "grouped"` çocukları tek biçime iner; grup başlığı seçilemez, etiketi olmayan seçenek değerini gösterir.'
185
185
  ]
@@ -615,7 +615,7 @@ export const mobileComponentKnowledge = {
615
615
  'Liste alttan açılır (`dropdown` emsali): gruplu listede (`optionType: grouped`) çocuklu satır `isSelectable` değilse başlıktır, gruplar hep açıktır (`isExpanded` okunmaz). Arama kutusu `search` olayına akış bağlıysa `search { searchText }` yayar ve listeyi akış yazar, değilse yerelde süzer. Liste açılınca `focus`, kapanınca (`Done` ya da perde) `blur`; arama doluyken kapanırsa ve `search` akışa bağlıysa web gibi `search { searchText: "" }` de yayılır (yeniden açılan liste eski sonuçla kalmasın).',
616
616
  'Üst sınır web gibi formun `maxItems` doğrulamasıdır, seçim kesilmez. Alanda en çok üç çip, gerisi "+n". İç roller `__trigger`, `__clear`, `__done`; satırlar `role="option"` + `data-value`.',
617
617
  'Yerel arama (akış bağlı değilken) `dropdown` gibi veri dilinin yereliyle ve işaretsiz katlanır: İngilizce uygulamada "in" "India"yı, "suk" "Sükkülent"i bulur (2026-09-29; öncesinde hep `tr-TR`).',
618
- 'Satırın görseli web `SelectItem` önceliğiyle: `prefixImage` > `prefixText` (zemini `color`) > `icon` (rengi `color`). Düşenler: `options[].tooltip`, `isExpanded`, `lookupMode` (gruplar hep açık; üzerine gelme ve ↗ lookup penceresi yok).'
618
+ 'Satırın görseli web `SelectItem` önceliğiyle: `prefixImage` > `prefixText` (zemini `color`) > `icon` (rengi `color`). Düşenler: `options[].tooltip`, `isExpanded` (gruplar hep açık; üzerine gelme yok). Mobil şema `lookupMode`’u web sınıfından devralır ama mobil çizici onu OKUMAZ: ↗ lookup penceresi açılmaz, yazılan değer etkisizdir.'
619
619
  ]
620
620
  },
621
621
  toast: {
@@ -768,7 +768,7 @@ export const mobileComponentKnowledge = {
768
768
  'Değer web gibi `{ value: base64 veri URL`si, fileName, type: "file" }` (çoklu kipte dizi, yeni seçim üstüne eklenir; boşaltma `{ type: "ref", value: null }`, çoklu kipte `[{ type: "ref", value: null }]`) — backend `TableFileParameter { value, type: ref|file|document, fileName }` biçiminin aynısı: `file` depoya yazılır, `ref` var olan dosyayı bağlar, `null` alanı boşaltır. Base64 sayfa durumunda ve istek gövdesinde taşındığı için `maxFileSizeMb` (mobil varsayılan 10) sıfırlanmaz. Kamera/galeri fotoğrafı uzun kenarı `maxImageDimension`a (varsayılan 1600 px; 0 = kapalı) indirilip JPEG olarak yeniden kodlanır — boyut kapısı küçültülmüş dosyaya bakar; belge seçiciden gelen dosyaya dokunulmaz. Fotoğraf arşivinden gelen HEIC iOS`ta JPEG`e çevrilir (`.jpg` — web`de Safari`nin yaptığı gibi), fotoğraflar `quality` ile sıkıştırılır. Görsel seçimler alanda küçük resimle görünür.',
769
769
  'Tür/boyut/adet kapısı web kuralıdır (tek uyarı, tür ihlali önce; uyarı metni uygulama dilinde). İç roller: kutu `<elementId>__field`, Gözat `__browse`, temizle `__clear`, çoklu kipte satır silme `__file-<n>__delete`. `isReadonly`/`isViewmode` Gözat`ı ve X`i gizler.',
770
770
  'Düşen: `style`.',
771
- 'f-13 (mobile özgü görünüm ve düzenleme): görsel dosya satırda 48 px küçük resimdir; tekli değerde satır "Değiştir" (`__file-0__change`, seçici yeniden açılır, değer yerinde değişir) ve kaldır taşır. `variant: tile` 1:1 kesikli "+ Ekle" karosu (`__add`) ve üç sütunlu küçük resim ızgarasıdır: dolu karoya basış değiştirir (`__file-<n>__change`), köşedeki × siler (`__file-<n>__delete`). `enableCrop` + `cropAspectRatio` cihazda sistem kırpıcısını açar (yalnız tek seçimde; iOS serbest oranı kesin uygulamaz; önizlemede kırpma yok), `rotateOnPick` (0/90/180/270) seçilen fotoğrafı döndürür. Kamera izni verilmezse değer değişmez, alanın altında Ayarlar bağlantılı not çıkar (`__settings`); alan geçersiz sayılmaz. Fotoğraf arşivi izin istemez (iOS PHPicker, Android sistem seçicisi).'
771
+ 'f-13 (mobile özgü görünüm ve düzenleme): görsel dosya satırda 48 px küçük resimdir; tekli değerde satır "Değiştir" (`__file-0__change`, seçici yeniden açılır, değer yerinde değişir) ve kaldır taşır. `variant: tile` 1:1 kesikli "+ Ekle" karosu (`__add`) ve üç sütunlu küçük resim ızgarasıdır: dolu karoya basış değiştirir (`__file-<n>__change`), köşedeki × siler (`__file-<n>__delete`). `enableCrop` + `cropAspectRatio` cihazda sistem kırpıcısını açar (yalnız tek seçimde; iOS serbest oranı kesin uygulamaz; önizlemede kırpma yok), `rotateOnPick` (0/90/180/270) seçilen fotoğrafı döndürür. Kamera izni verilmezse değer değişmez, alanın altında Ayarlar bağlantılı not çıkar (`__settings`); alan geçersiz sayılmaz. Kamera iznini taşımayan derlemede (`source: camera`) izin istenmez, not Ayarlar bağlantısız "bu uygulamada kamera kullanılamıyor" der. Fotoğraf arşivi izin istemez (iOS PHPicker, Android sistem seçicisi).'
772
772
  ],
773
773
  propNotes: {
774
774
  source: 'Dosyanın geldiği yer: `files` (belgeler, varsayılan), `library` (fotoğraf arşivi), `camera`, `ask` (Kamera / Fotoğraflar / Dosyalar sorulur). `library` ve `camera` yalnız fotoğraf verir.',
@@ -1643,12 +1643,34 @@ function gapWalk(parentLabel, children, code, onlyParents, isRoot, warnings, par
1643
1643
  * sayfadan ayrılmadan koşar (`Runtime.svelte initDestroyGuard`). Platform uydurma adı 200 ile
1644
1644
  * kaydeder ama runtime eşleştiremez ve bağ sessizce ölü kalır (PAGE-008'in sayfa/layout ikizi).
1645
1645
  */
1646
- const SCREEN_EVENT_NAMES = new Set(['mount', 'destroy']);
1647
- export function invalidScreenEventNames(events, code) {
1646
+ const SCREEN_EVENT_NAMES = ['mount', 'destroy'];
1647
+ /**
1648
+ * 38 S-15 / kök ADR 0015: mobil SAYFA bir olay daha taşır, `focus` — ekran ilk göründüğünde ve her geri
1649
+ * dönüşte (yığından geri, sekme değişimi) koşar; yığındaki ekran bağlı kaldığı için `mount` geri dönüşte
1650
+ * koşmaz. Web her gezinmede sayfayı baştan kurar (`Runtime.svelte` focus'u tanımaz), layout'un focus'u
1651
+ * yoktur. Ayna: `ng-apps-ui-native` `SCREEN_LIFECYCLE_EVENTS` (parite: `test/page-meta.test.ts`).
1652
+ */
1653
+ const MOBILE_PAGE_EVENT_NAMES = [...SCREEN_EVENT_NAMES, 'focus'];
1654
+ const SCREEN_EVENT_HINTS = {
1655
+ mount: 'mount (yüklendiğinde)',
1656
+ destroy: 'destroy (ayrılırken; temizlik akışları için)',
1657
+ focus: 'focus (ekran her göründüğünde — ilk açılış ve geri dönüş; listeyi yeniden okumak için)'
1658
+ };
1659
+ export function screenEventNames(kind, platform) {
1660
+ return kind === 'page' && platform === 'mobile' ? MOBILE_PAGE_EVENT_NAMES : SCREEN_EVENT_NAMES;
1661
+ }
1662
+ /** Olaylardan biri yalnız mobil sayfada geçerliyse karar uygulamanın platformunu ister (page_update şemasız yazımı). */
1663
+ export function screenEventsNeedPlatform(events) {
1664
+ return asObjects(events).some(event => !SCREEN_EVENT_NAMES.includes(String(event.name ?? '')) && MOBILE_PAGE_EVENT_NAMES.includes(String(event.name ?? '')));
1665
+ }
1666
+ export function invalidScreenEventNames(events, code, names = SCREEN_EVENT_NAMES) {
1667
+ const valid = names.map(name => SCREEN_EVENT_HINTS[name] ?? name).join(' · ');
1648
1668
  return asObjects(events)
1649
1669
  .map(event => String(event.name ?? ''))
1650
- .filter(name => name !== '' && !SCREEN_EVENT_NAMES.has(name))
1651
- .map(name => `${code}: sayfa/layout düzeyinde '${name}' diye bir olay YOK — geçerli adlar: mount (yüklendiğinde) · destroy (ayrılırken; temizlik akışları için). Platform kaydı kabul eder ama bağ sessizce ölü kalır.`);
1670
+ .filter(name => name !== '' && !names.includes(name))
1671
+ .map(name => MOBILE_PAGE_EVENT_NAMES.includes(name)
1672
+ ? `${code}: '${name}' yalnız mobil uygulamanın SAYFASINDA vardır (layout'ta ve web sayfasında yok; web sayfayı her gezinmede baştan kurar, mount yeter) — burada geçerli adlar: ${valid}.`
1673
+ : `${code}: sayfa/layout düzeyinde '${name}' diye bir olay YOK — geçerli adlar: ${valid}. Platform kaydı kabul eder ama bağ sessizce ölü kalır.`);
1652
1674
  }
1653
1675
  const GUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1654
1676
  /**
@@ -1716,9 +1738,9 @@ function collectDesktopPositions(nodes, into) {
1716
1738
  * görünen sıraya yerleştir, x=0, birikimli y). Burada aynısı yapılır: akış kabında `index` GÖRÜNEN
1717
1739
  * sıradaki yerdir (eski kayıtlarda dizi sırası çizimle aynı değildir, Studio sürüklerken diziyi
1718
1740
  * değil y'yi yazar). Op'lar okunan şemanın kopyasına sırayla uygulanır; eklenen düğümün son konumu
1719
- * kendi gövdesine, var olan kardeşlerinki yamanın sonuna `move_component {target, position}` olarak
1720
- * yazılır. Platform position'ı düğümün İLK bloğuna yazar: ilk bloğu desktop olmayan kardeş atlanır ve
1721
- * uyarılır. Izgara kapta çizim mutlak koordinattır; sona eklemeyen `index` orada PAGE-033 uyarısıdır.
1741
+ * kendi gövdesine, var olan kardeşlerinki yamanın sonuna `move_component {target, breakpoint: 1024,
1742
+ * position}` olarak yazılır. `breakpoint` açık yazılır: ilk bloğu mobil kırılım olan kardeşte mobil
1743
+ * konum ezilmez (PAGE-033, K9). Izgara kapta çizim mutlak koordinattır; sona eklemeyen `index` orada PAGE-033 uyarısıdır.
1722
1744
  * `index` verilmemiş op'a dokunulmaz.
1723
1745
  */
1724
1746
  export function flexIndexOrder(schema, ops) {
@@ -1831,11 +1853,7 @@ export function flexIndexOrder(schema, ops) {
1831
1853
  const before = original.get(elementId);
1832
1854
  if (!body && !moveTargets.has(elementId) && before && before.x === final.x && before.y === final.y)
1833
1855
  continue;
1834
- if (Number(asObjects(node.blocks)[0]?.breakpoint) !== DESKTOP_BREAKPOINT) {
1835
- warnings.push(`PAGE-033 (uyarı): '${elementId}' flex sırasına göre y:${final.y}'e taşınmalıydı ama ilk bloğu desktop (1024) değil — platform move_component'in position'ını İLK bloğa yazar, yanlış kırılımı bozmamak için konum yazılmadı. Düğümü page_update(schema) ile düzeltin.`);
1836
- continue;
1837
- }
1838
- trailing.push({ op: 'move_component', target: elementId, position: { x: final.x, y: final.y } });
1856
+ trailing.push({ op: 'move_component', target: elementId, breakpoint: DESKTOP_BREAKPOINT, position: { x: final.x, y: final.y } });
1839
1857
  moved.push(`${elementId} y:${before?.y ?? '?'}→${final.y}`);
1840
1858
  }
1841
1859
  return { ops: [...sent, ...trailing], moved, warnings };
@@ -116,7 +116,7 @@ export const mobileProfile = Object.freeze({
116
116
  },
117
117
  style: { freeStyle: false, hoverStyle: false, resolver: 'native' },
118
118
  steps: {
119
- registered: new Set([...ENGINE_STEP_TYPES, 'alert', 'confirm', 'redirect', 'navigate', 'storage', 'setcookie', 'download', 'share', 'haptic', 'location', 'notification-permission', 'notification-schedule', 'notification-cancel', 'permission', 'open-settings', 'ad-show']),
119
+ registered: new Set([...ENGINE_STEP_TYPES, 'alert', 'confirm', 'redirect', 'navigate', 'storage', 'setcookie', 'download', 'share', 'haptic', 'location', 'notification-permission', 'notification-schedule', 'notification-cancel', 'permission', 'open-settings', 'ad-show', 'back']),
120
120
  aliases: STEP_ALIASES,
121
121
  infoOnly: new Set(['download', 'setcookie'])
122
122
  },
@@ -20,7 +20,7 @@ export const webProfile = Object.freeze({
20
20
  },
21
21
  style: { freeStyle: true, hoverStyle: true, resolver: 'css' },
22
22
  steps: {
23
- registered: new Set([...ENGINE_STEP_TYPES, 'alert', 'confirm', 'redirect', 'download', 'storage', 'setcookie', 'share', 'haptic', 'location', 'notification-permission', 'notification-schedule', 'notification-cancel', 'permission', 'open-settings', 'ad-show']),
23
+ registered: new Set([...ENGINE_STEP_TYPES, 'alert', 'confirm', 'redirect', 'download', 'storage', 'setcookie', 'share', 'haptic', 'location', 'notification-permission', 'notification-schedule', 'notification-cancel', 'permission', 'open-settings', 'ad-show', 'back']),
24
24
  aliases: STEP_ALIASES,
25
25
  infoOnly: new Set()
26
26
  },