# Low-Code Genişletilmiş Referans (JSON Kolon ve Modül Sözleşmeleri) `lowcode.instructions.md` **nasıl üretileceğini** anlatır; bu dosya **neyin ayarlanabildiğini** tek tek listeler. Orada "bunun bir JSON kolonu var" denen her şeyin alan alan karşılığı burada durur: ListForm/ListFormField'ın tüm JSON kolonları, grafik sözleşmesi, Visual Designer envanteri ve zamanlanmış e-posta modülü. Son bölüm (§12) Saas, Administration ve İK (Hr) menülerindeki **hazır gelen bütün ekranları** ve bunların yeni bir komponentte nasıl kullanılacağını listeler. **Kullanım sırası:** `ai.instructions.md` → `lowcode.instructions.md` → **bu dosya** → `README.md`. Bir istek geldiğinde önce ana dosyadaki karar sırasını işlet; "hangi anahtarı yazacağım?" sorusunda buraya bak. **Kaynak:** Buradaki her tablo `api/src/Sozsoft.Platform.Application.Contracts/ListForms/**` (DTO), `api/src/Sozsoft.Platform.Domain/Entities/Tenant/ListForm/ListForm{,Field}.cs` (entity), `ui/src/views/{list,form}/**` (alanın gerçekten okunup okunmadığı) ve §12 için `configs/seeds/data/App.Menus.*.json` + `ListFormSeeder_*.cs` dosyalarından çıkarılmıştır. Çelişki hâlinde **kod esastır**; bu dosya güncellenir. **Ortak kurallar** - Bütün `*Json` kolonları veritabanında **tek satır JSON string** olarak durur. Wizard seed dosyasının `ListForm`/`Fields` bölümleri ekranın DTO sözleşmesini taşıdığı için orada bu kolonlar kaçışlanmış metin değil, tipli karşılıkları (`SchedulerOptionJson` → `SchedulerOptionDto`) olarak yer alır; kaçışlama yalnızca `EditorOptions`, `EditorScript`, `LookupQuery` gibi gerçekten metin olan alanlar içindir. - Varsayılan değeri olan bir alanı tekrar yazmak zorunda değilsin; yalnızca farklı olanı yaz. - `Accepted Values` yazan yerlerde değer birebir eşleşmelidir (büyük/küçük harf dahil). - **Bilinmeyen anahtar sessizce düşer — tarayıcıya hiç ulaşmaz.** `GridOptionsDto` (ekran) ve `ColumnFormatDto` (alan) `*Json` kolonlarının ham metnini `[JsonIgnore]` ile gizler ve istemciye yalnızca tipli DTO'ya (`SchedulerOptionJson` → `SchedulerOptionDto`) çözülmüş hâlini yollar. Dolayısıyla: - DTO'da olmayan bir DevExtreme seçeneğini `*Json`'a yazmak **işe yaramaz**; DTO'ya alan eklemek Core'dur ve yalnızca "core sisteme ekle" komutuyla yapılır (`lowcode` §0.8); eklenince bu dosya da güncellenir. - Çözme `JsonSerializer`'ın varsayılan ayarlarıyla yapılır: anahtarlar **PascalCase ve büyük/küçük harfe duyarlıdır**. `"visible"` yazılan yerde `"Visible"` beklenir; küçük harfli anahtar hata vermez, sessizce düşer. İstemcinin gördüğü camelCase hâl API çıktısıdır, seed'e yazılmaz. - Yanlış **tipte** yazılan değer (`"3"` yerine `3`, dizi yerine metin) `JsonSerializer` tarafında `JsonException` fırlatır ve ekranın metadata isteği tamamen düşer — sessiz değil, gürültülü bir hatadır. Tipleri tablodaki gibi yaz. - Bu kural yalnızca alan seviyesindeki `ListFormField.EditorOptions` için geçmez: orası ham metin olarak taşınır, tanımadığı anahtarı doğrudan editöre geçirir (§9.8). --- ## 0. Üretim yetenek envanteri — bir komponent üretirken tara Bir ekran/komponent üretirken **her katmanı sırayla geç**. Cevabı "gerekmiyor" olan katmanı atla, ama hiçbirini görmeden bırakma; kullanıcı "şunu da yapabilir miydik?" dediğinde cevap "kod yazmamız gerekir" olmasın. | # | Katman | Sorulacak soru | Nerede | | --- | --- | --- | --- | | 0 | **Hazır yetenek** | Bu talebin karşılığı Saas/Administration/İK menüsünde zaten var mı? | §12.6 | | 1 | **Veri** | Tablo/view var mı, join gerekiyor mu, anahtar ve tipi ne? | `lowcode` §8 · §9.6 | | 2 | **Ekran türü** | Liste mi, tek kayıt formu mu, serbest yerleşim mi? Kaç görünüm açılacak? | `lowcode` §0.6.1 · §9.8 · §5.4 | | 3 | **Sütunlar** | Editör tipi, biçim, hizalama, genişlik, band, gizli/aktif | `lowcode` §4.3 · §9.7 | | 4 | **Lookup** | Sabit liste mi, sorgu mu, servis mi? Cascade var mı? | `lowcode` §4.3.4 | | 5 | **Varsayılanlar** | Kullanıcı/tarih/tenant/sequence/sorgu ile otomatik dolacak alanlar | `lowcode` §4.3.5 · §9.9 | | 6 | **Alan davranışı** | Hesap, kopyalama, temizleme, koşullu kilit, uyarı, servis çağrısı | `lowcode` §5 | | 7 | **Editör görünümü** | Maske, ondalık, tarih biçimi, arama, çoklu seçim, görsel yükleme, kamera/barkod/konum | `lowcode` §6 · §9.8 | | 8 | **Doğrulama** | Zorunluluk, aralık, desen, karşılaştırma + sunucu tarafı kontrol | `lowcode` §4.3.6 · §4 | | 9 | **Filtre/arama** | Filtre satırı, başlık filtresi, arama paneli, ek filtre çubuğu, grup paneli | §2.6 · `lowcode` §4.3.8 | | 10 | **Özet** | Toplam/adet satırı, grup özetleri, pivot | §9.5 · §6 | | 11 | **Biçimlendirme** | Koşullu satır/hücre rengi, CSS sınıfı | `lowcode` §4.3.7 | | 12 | **Düzenleme deneyimi** | Popup/satır/hücre/batch, form grupları, kopyalama, toplu silme | §3 | | 13 | **Aksiyonlar** | Hazır toolbar/satır butonları, URL parametreleri, not/ek/geçmiş, içe-dışa aktarma; özel butonlar: adres, diyalog, script, yetki, koşullu görünürlük (satır) ve seçime göre aktiflik (toolbar) | §5.8 · `lowcode` §4.9 | | 14 | **Yazma mantığı** | Özel SQL, before/after kancaları, farklı servis adresi | §4 | | 15 | **Ana-detay** | Alt form sekmeleri ve ilişki alanları | §5.2 | | 16 | **Gösterge** | Üstte KPI kartları, grafik | §5.3 · §8 | | 17 | **Süreç** | Onay akışı, bilgilendirme, durum alanları | `lowcode` §4.7 | | 18 | **Otomasyon** | Zamanlanmış iş, zamanlanmış e-posta, bildirim kuralı | `lowcode` §9.10–§9.11 · §11 | | 19 | **Çıktı** | Hazır grid raporu ya da `Raporlar` kategorisinde `DynamicReport` şablonu (kayıt alanları `{{AlanAdi}}`) | `lowcode` §9.12 | | 20 | **Yetki** | Ekran, alan, komut ve buton yetkileri; menü görünürlüğü | §5.1 · `lowcode` §4.2 | | 21 | **Tenant/şube/satır kısıtı** | `IsTenant`, `IsBranch`, `IsOrganizationUnit`, sorgularda kırılım, rol/kullanıcıya `ServerWhere` | `lowcode` §4.2 · §2.5 | | 22 | **Dil** | Menü, başlık, alan, buton, mesaj metinleri EN + TR | `lowcode` §4.2 | | 23 | **Kullanıcı tercihi** | Grid durumunun saklanması, kullanıcı/rol/dil varyantı | §2.5 · §5.7 | | 24 | **Seed** | Üretilen her artefaktın dosyası + yalnızca DB'de yaşayan kayıtların geri getirici script'i | `lowcode` §0.2 · §9.14 | Serbest yerleşimli bir komponent (Visual Designer) üretiyorsan 0–2 ve 13–24 aynen geçerlidir; 3–12 yerine tasarımcı envanteri (§10.1), `Form` sözleşmesi (§10.2), veri kaynağı/filtre sözleşmesi (§10.4), bileşen prop sözleşmeleri (§10.5–§10.9), olay yükleri (§10.10) ve event script'leri (`lowcode` §7.8) kullanılır. 20. katman (yetki) orada iki yerdedir: düğüm başına `designerPermission` ve `Form`'un dört komut yetkisi. Katmanların çoğunda cevap yeni bir artefakt değil, platformun **hazır bir ekranıdır**: numaratör, bildirim, zamanlanmış iş, rapor, ayar, dil, yetki, şube, kısıt, dosya. Hepsinin envanteri §12'de — "kod yazmamız gerekir" demeden önce §12.6'ya bak. --- ## 1. ListForm JSON kolon haritası Bir List ekranının bütün davranışı bu kolonlardadır. "Şunu yapabilir miyiz?" sorusunun cevabı önce bu tabloda aranır. Aşağıdaki tablo `*Json` bloklarını listeler. Bunların **öncesinde** ekranın düz kolonları gelir ve çoğu isteğin cevabı aslında oradadır: | Kolon | Varsayılan | Ne yapar | | --- | --- | --- | | `ListFormCode` | — | Ekranın kimliği; route, metadata ve varyantlar bu koda bağlanır | | `DataSourceCode` | `"Default"` | Hangi bağlantıdan okunacağı (`Sas_H_DataSource`) | | `SelectCommandType` | `1` Table | `1` Table · `2` View · `3` TableValuedFunction · `4` Query · `5` StoredProcedure | | `SelectCommand` | — | Tablo/view adı, fonksiyon çağrısı ya da sorgu metni | | `TableName` | `SelectCommand` | Üretilen `SELECT`'te kolonların ve join'lerin önüne yazılan **alias**. Boşsa `SelectCommand` kullanılır. `4` Query'de **zorunludur**: sorgu `FROM (…) AS "{TableName}"` biçiminde türetilmiş tablo olarak sarılır ve boş bırakılırsa sorgu metni alias olur (SQL hatası). Wizard/MCP `deploy_wizard` Query ekranına `"Q"` (`WizardConsts.QuerySourceAlias`) yazar; fonksiyon çağrısında da kısa bir ad yaz | | `KeyFieldName` · `KeyFieldDbSourceType` | — · `16` String | Anahtar kolon ve `System.Data.DbType` karşılığı. `4` Query'de ikisi de **zorunludur** (kolonlar katalogdan okunamaz); wizard/`deploy_wizard` anahtarsız Query ekranını reddeder, tip boşsa (`0`) seçilen alanlardaki karşılığından tamamlar. Ana tablo (en dıştaki `FROM`) `IsDeleted` taşıyorsa `DefaultFilter` yazılır ve sorgu `IsDeleted`'i seçmek zorundadır | | `SelectFieldsDefaultValueJson` | — | ListForm editöründe (Veritabanı › Select) düzenlenir ama runtime **okumaz** (`DefaultValueManager` Select'te `FormFieldsDefaultValueJson`'ı kullanır). Okuma koşulu `DefaultFilter`'a token'la yazılır (`lowcode` §4.3.5) | | `DefaultFilter` | — | Her sorgunun sonuna `AND` ile eklenen sabit `WHERE` koşulu. `DefaultValueHelper` üzerinden geçer, yani `@USERID`/`@TENANTID`/`@NOW` gibi token'lar çözülür. Soft delete'li tabloda (`IsDeletedField: true`) wizard buraya `"IsDeleted" = 'false'` yazar | | `SortMode` | — | **Metin**: `"single"` \| `"multiple"` \| `"none"` (`PlatformConsts.GridOptions.SortMode*`; wizard `single` yazar) | | `PageSize` | `50` (DB) | Sayfa başına kayıt; `0` = tümü. C# varsayılanı `0`, veritabanı varsayılanı `50`; boşsa grid `20` kullanır | | `Name` · `Title` · `Description` | — | Ekran adı, başlığı, açıklaması — **dil anahtarı** yazılır | | `IsTenant` · `IsBranch` · `IsOrganizationUnit` | `IsMultiTenant` · `false` · `false` | Otomatik kırılım filtresi; `IsTenant` ayrıca bağlantı çözümünü belirler (`lowcode` §9.3.3) | | `SeedFilePath` · `SeedSyncInsert` · `SeedSyncUpdate` · `SeedSyncDelete` | — · `false` | Ekranın verisini `data/` altındaki dosyaya aynalar; `.json` uzantılı değer tek dosya, uzantısız değer klasör (`lowcode` §9.15) | | Kolon | Ne yapılandırır | Bölüm | | --- | --- | --- | | `ColumnOptionJson` | Grid genel görünümü (kenarlık, satır rengi, kolon seçici, sabitleme) | §2.1 | | `CommandColumnJson` | Toolbar ve satır butonları | `lowcode` §4.9 | | `RowJson` | Satır yüksekliği ve metin kaydırma | §2.2 | | `PagerOptionJson` | Sayfalama şeridi | §2.3 | | `SelectionJson` | Seçim modu ve checkbox davranışı | §2.4 | | `StateStoringJson` | Kullanıcının grid tercihlerinin saklanması | §2.5 | | `FilterRowJson` · `HeaderFilterJson` · `FilterPanelJson` · `SearchPanelJson` · `GroupPanelJson` | Filtre/arama/gruplama panelleri | §2.6 | | `ExtraFilterJson` | Ekran üstü ek filtre çubuğu | `lowcode` §4.3.8 | | `EditingOptionJson` | Düzenleme modu, izinler, popup ayarları | §3.1 | | `EditingFormJson` | Düzenleme formunun grup/sekme yerleşimi | §3.2 | | `InsertCommand` · `UpdateCommand` · `DeleteCommand` (+ `*Before`/`*After`) | Özel SQL komutları ve kancalar | §4 | | `*FieldsDefaultValueJson` | Otomatik doldurulan alanlar | `lowcode` §4.3.5 | | `*ServiceAddress` | Yazma işlemini karşılayan uç | §4.3 | | `PermissionJson` | Ekran yetkileri | §5.1 | | `SubFormsJson` | Alt form sekmeleri | §5.2 | | `WidgetsJson` | KPI kartları | §5.3 | | `WorkflowJson` | Onay akışı | `lowcode` §4.7 | | `LayoutJson` | Açık görünümler ve varsayılan | §5.4 | | `PivotOptionJson` | Pivot görünümü | §6 | | `TreeOptionJson` · `GanttOptionJson` · `SchedulerOptionJson` · `TodoOptionJson` | Ağaç/Gantt/Takvim/Kanban eşlemeleri | §7 | | `SeriesJson`, `CommonJson` ve diğer 19 grafik kolonu | Chart görünümü (21 kolon; `DataSourceJson` okunmaz) | §8 | | `ExportJson` | Grafik dışında **Grid/Pivot dışa aktarmayı da** açar: `Enabled` + `AllowExportSelectedData`; Grid yalnız `pdf`/`xlsx`, Pivot yalnız `xlsx`. Grid düğmesi ayrıca `PermissionJson.E` ister | §8.4 · §5.8 | | `CustomJsSourcesJson` · `CustomStyleSourcesJson` | Sayfaya enjekte edilen JS/CSS | §5.5 | | `Width` · `Height` · `FullHeight` | Ekran boyutu. `Height > 0` sabit piksel yüksekliği verir. Grid/Tree/Gantt/Scheduler'da `FullHeight` bayrağı görünümü `calc(100vh - …)` ile sayfaya yayar; **TodoBoard** bayrağa bakmaz, `Height` verilmediyse panoyu her zaman sayfanın kalan yüksekliğine yayar (kolonlar kendi içinde kayar), alt form olarak açıldığında ise içeriği kadar yer kaplar. | — | | `ListFormType` · `IsSubForm` · `SubFormsListFormType` · `ShowNote` | Ekranın türü ve alt form davranışı | §5.6 | | `CultureName` · `UserId` · `RoleId` | Ekranın dil varyantı (`UserId`/`RoleId` okunmaz — alan seviyesinde çalışır) | §5.7 | --- ## 2. Grid görünümü ### 2.1 `ColumnOptionJson` (`GridColumnOptionDto`) | Anahtar | Varsayılan | Anlam | | --- | --- | --- | | `ShowBorders` | `true` | Dış kenarlık | | `ShowRowLines` | `true` | Satır çizgileri | | `ShowColumnLines` | `false` | Kolon çizgileri | | `RowAlternationEnabled` | `true` | Tek/çift satır renklendirme | | `AllowColumnReordering` | `true` | Kullanıcı kolon sırasını değiştirebilir | | `AllowColumnResizing` | `true` | Kolon genişliği değiştirilebilir | | `ColumnResizingMode` | `"widget"` | `nextColumn` \| `widget` | | `ColumnAutoWidth` | `false` | İçeriğe göre genişlik | | `ColumnFixingEnabled` | `false` | Kolon sabitleme menüsü | | `ColumnChooserEnabled` | `false` | Kolon seçici düğmesi | | `ColumnChooserMode` | `"dragAndDrop"` | `dragAndDrop` \| `select` | | `ColumnHidingEnabled` | `true` | Dar ekranda kolon gizleme | | `HoverStateEnabled` | `false` | Satır üzerine gelince vurgulama | | `FocusedRowEnabled` | `true` | Odaklı satır | | `ShowColumnHeaders` | `true` | Başlık satırı | | `RtlEnabled` | `false` | Sağdan sola | ### 2.2 `RowJson` (`GridRowDto`) | Anahtar | Varsayılan | Anlam | | --- | --- | --- | | `RowHeight` | `"auto"` | Satır yüksekliği (CSS) | | `WhiteSpace` | `"normal"` | Uzun metnin kaydırılması | | `OverflowWrap` | `"break-word"` | Kelime kırma | ### 2.3 `PagerOptionJson` (`GridPagerOptionDto`) Adı yalnızca sayfalamayı çağrıştırsa da bu blok **kaydırma ve yükleniyor panelini** de taşır. | Anahtar | Varsayılan | Anlam | | --- | --- | --- | | `Visible` | `true` | Sayfalama şeridi | | `ShowPageSizeSelector` | `false` | Sayfa boyu seçici | | `ShowInfo` | `false` | "Page 1 of 4 (37 items)" bilgisi — **Grid okumaz** (sabit kapalı); Tree (boşsa `true`) ve Card okur | | `ShowNavigationButtons` | `true` | İleri/geri düğmeleri | | `AllowedPageSizes` | `"10,20,50,100"` | **Virgüllü metin**, dizi değil | | `InfoText` | `"Page {0} of {1} ({2} items)"` | `{0}` sayfa · `{1}` toplam sayfa · `{2}` kayıt — yalnız Tree ve Card | | `DisplayMode` | `"adaptive"` | `adaptive` \| `compact` \| `full` | | `ScrollingMode` | `"standard"` | `standard` \| `virtual` \| `infinite` — uzun listelerde `virtual` | | `LoadPanelEnabled` | `"auto"` | **Metin** alanıdır: `"auto"` \| `"true"` \| `"false"`. İstemci metni boolean'a çevirir (`resolveLoadPanelEnabled`); Grid, Tree, Pivot ve Card aynı kuralı kullanır | | `LoadPanelText` | `"Loading..."` | Yükleniyor metni | Sayfa başına kayıt sayısı ekranın `PageSize` alanındadır; `0` = tümü. `ScrollingMode` ile `PageSize` birlikte düşünülür: `virtual`/`infinite` seçilip `PageSize: 0` yazılırsa sayfalama devre dışı kalır ve tüm küme tek istekte çekilir. `ScrollingMode` ve `LoadPanel*` Pivot'ta da okunur. ### 2.4 `SelectionJson` (`SelectionDto`) | Anahtar | Varsayılan | Değerler | | --- | --- | --- | | `Mode` | `"none"` | `single` \| `multiple` \| `none` | | `AllowSelectAll` | `false` | | | `SelectAllMode` | `"allPages"` | `allPages` \| `page` | | `ShowCheckBoxesMode` | `"always"` | `always` \| `none` \| `onClick` \| `onLongTap` | Toolbar butonları seçili satırlarla çalışır (`lowcode` §4.9); onay akışı varsa wizard `single` yazar. Tree yalnız `Mode`'u okur (`recursive` `TreeOptionJson.RecursiveSelection`'dan gelir); `AllowSelectAll`, `SelectAllMode`, `ShowCheckBoxesMode` Grid ve Card'da çalışır, `AllowSelectAll` Pivot'un başlık filtresinde "tümünü seç"i de açar. `SubFormsListFormType: "List"` ekranında alt form sekmeleri seçili satırla beslendiği için `Mode` `single`/`multiple` olmalıdır (§5.6). ### 2.5 `StateStoringJson` (`StateStoringDto`) | Anahtar | Varsayılan | Anlam | | --- | --- | --- | | `Enabled` | `true` | Toolbar'daki **Grid durumunu kaydet / sıfırla** menüsünü açar. Kayıt **elle** yapılır (Grid/Tree/Pivot'ta otomatik kayıt kapalı); kayıtlı durum `Enabled`'dan bağımsız her açılışta yüklenir. Pivot'un kaydet/sıfırla düğmeleri her zaman görünür | | `Type` | `"custom"` | **Okunmaz** — durum her zaman sunucuya (`ListFormCustomization`) yazılır; editör de yalnız `custom` sunar | | `StorageKey` | — | Anahtar; boşsa sunucu `listFormGridStorage-{ListFormCode}` üretir | | `SavingTimeout` | `5000` | Yalnız **TodoBoard**'da otomatik kayıt bekleme süresi — **`[Range(1500, 5000)]`**, dışına çıkan değer doğrulamada reddedilir | Durum sunucuda `ListFormCustomization` kaydı olarak (`ListFormCustomizationTypeEnum.GridState = 2`) saklanır, başka tarayıcıdan girildiğinde de korunur. Kayıt **kullanıcı > rol > genel** önceliğiyle çözülür: `UserId`/`RoleId` boş `GridState` herkese, `RoleId` dolu olan o role varsayılan düzen verir — `UserUiFilter` (paylaşılan kayıtlı filtre) için de aynısı geçerlidir. Seçili görünüm (grid/card/…) ve Card'ın kart başına kolon sayısı ise sunucuda değil **tarayıcının localStorage**'ındadır. Aynı `GridState` kaydı görünüm başına ayrı `FilterName` taşır: `list-`, `tree-`, `pivot-`, `todo-`, `gantt-` ve form sayfası için `form-{StorageKey}` (`useListFormStateStoring`). Form sayfasının state'i kullanıcının **alan sırasıdır** (`{"fieldOrder": {"Alan": 1, …}}`, grup içi sıra): araç çubuğundaki **Form Düzeni** paneli (`FormFieldOrderPanel`) alanları sürükleyerek sıralar, form her taşımada canlı değişir, Kaydet state'e yazar, Sıfırla son kayda, Fabrika Ayarlarına Dön `EditOrderNo` sırasına döner (state boşaltılır). `EditOrderNo` ve alanın grubu değişmez; panel yalnızca `Enabled` açıkken görünür. Aynı tablonun diğer türleri: `UserUiFilter = 1` (kaydedilmiş filtre, §2.6), `ServerJoin = 3` ve `ServerWhere = 4` — `UserId`/`RoleId` boşsa herkese, doluysa o rol/kullanıcıya sunucu tarafında eklenen join ve `WHERE` parçası (`SelectQueryManager`; `ServerWhere` token'ları `DefaultValueHelper` ile çözülür). **Satır bazlı veri kısıtının** kod yazmadan kurulduğu yer burasıdır; ListForm editörünün **Kişiselleştirme** sekmesinden yazılır. **İstemci filtresi sözleşmesi** (`list-form-select/select` `filter` parametresi; grid, rapor, widget ve MCP `query_list` aynı yoldan geçer): yalnızca DevExtreme filtre dizisi kabul edilir — `["Alan","op",değer]`, `["Alan",değer]`, `[koşul,"and"|"or",koşul]`, `["!",koşul]`, iç içe gruplar. Alan ekranın alanı ya da anahtar alan olmalıdır (değilse koşul `1 = 1` olur), operatör `= <> < <= > >= contains notcontains startswith endswith` dışındaysa ve JSON bozuksa istek reddedilir; değer her zaman parametredir. Filtre tek grup olarak paranteze alınır, ardından kiracı/şube/OU koşulları `AND` ile eklenir. Toplam/grup alanları da ekranın alanlarından, toplam türü `sum avg min max count`'tan seçilir. "Tüm kayıtları sil" (`createDeleteQuery`) ekranın silme yetkisini ve `AllowAllDeleting`'i birlikte ister. ### 2.6 Filtre, arama ve gruplama panelleri **`FilterRowJson`** (`GridFilterRowDto`): `Visible` (`false`), `ApplyFilter` (`auto` \| `onClick`), `AllowUserUiFilterSave` (varsayılan `true` — kullanıcı kendi filtresini kaydedebilir; kayıt `ListFormCustomizationTypeEnum.UserUiFilter = 1` olarak tutulur). `AllowUserUiFilterSave` aynı zamanda toolbar'daki **Rapor › Liste** düğmesini ve kayıtlı filtre seçim kutusunu açar; `ApplyFilter: onClick` filtre satırına **Filtre uygula** düğmesi ekler. Filtre uygulanınca başlık paneli renkli kenarlık alır (kayıtlı durum varsa farklı renk). **`HeaderFilterJson`** (`GridHeaderFilterDto`): `Visible` (`false`), `AllowSearch` (`false`), `Height` (`325`), `Width` (`252`), `SearchTimeout` (`500`). Grid, Tree ve Gantt yalnız `Visible`'ı okur (arama kolon bazında `ColumnHeaderJson.AllowSearch`'tedir); diğer dört anahtar yalnız Card ve Pivot'ta çalışır. **`FilterPanelJson`** (`FilterPanelDto`): `Visible` (`false`), `FilterEnabled` (`false`), `Texts.ClearFilter` (`"Clear"`) / `Texts.CreateFilter` (`"Create Filter"`) / `Texts.FilterEnabledHint` (`"Enable the filter"`), `CustomizeText`. Grid ve Tree yalnız `Visible`'ı okur; `Texts` yalnız Card'da çalışır; `FilterEnabled` ve `CustomizeText` **hiçbir görünümde okunmaz**. **`SearchPanelJson`** (`SearchPanelDto`): `Visible` (`false`), `Width` (`160`). **`GroupPanelJson`** (`GroupPanelDto`): `Visible` (`false`), `AutoExpandAll` (`false`). `AutoExpandAll: true` **fazladan bir sorgu daha** çalıştırır. --- ## 3. Düzenleme ### 3.1 `EditingOptionJson` (`GridEditingDto`) | Anahtar | Varsayılan | Anlam | | --- | --- | --- | | `Mode` | `"popup"` | `batch` \| `cell` \| `row` \| `form` \| `popup` | | `RefreshMode` | `"full"` | `full` \| `reshape` \| `repaint` | | `AllowAdding` / `AllowUpdating` / `AllowDeleting` | `false` | Aksiyon izinleri (yetki ayrıca `PermissionJson`: Ekle `C`, Düzenle `U`, Sil `D`). `AllowDeleting` + `D` toolbar'a **Seçilileri sil** düğmesi de ekler (2+ satır seçiliyken görünür). `Mode: batch` + `AllowUpdating` + `U` toolbar'a Kaydet/Geri al getirir | | `AllowAllDeleting` | `false` | **Tümünü sil (filtreli)** düğmesi: filtredeki bütün kayıtları, önce sayıyı gösterip onayla siler; `AllowDeleting` + `D` de gerekir | | `AllowDuplicate` | `false` | Satırda **Kopyala**: onay sorar, `list-form-data/duplicate` (`InsertCommand` + kancalar + `InsertFieldsDefaultValueJson`). Düğme **`C`** (Create) yetkisi ister | | `AllowDetail` | `false` | Satırda Detay düğmesi — `/admin/form/{kod}/{id}` yeni sekmede; **`U`** yetkisi ister (`R` değil) | | `OpenDetailAfterInsert` | `false` | Popup'tan eklenen kayıt kaydedilince aynı sekmede kaydın form sayfasına (`/admin/form/{ListFormCode}/{Id}`) geçilir. Grid, Tree, Card ve Scheduler'da çalışır; anahtar insert yanıtından alınır, dönmezse geçilmez. Wizard cevabında da aynı adla vardır (`ListFormWizardDto.OpenDetailAfterInsert`); ne zaman açılacağı `lowcode` §4.2 Davranış tablosunda | | `ConfirmDelete` | `true` | Silme onayı | | `UseIcons` | `false` | Aksiyonlar ikon olarak | | `NewRowPosition` | `"viewportTop"` | `first` \| `last` \| `pageTop` \| `pageBottom` \| `viewportTop` \| `viewportBottom` | | `StartEditAction` | `"click"` | `click` \| `dblClick` | | `SelectTextOnEditStart` | `false` | Düzenlemeye girince metni seç | | `EditColumnName` | — | **Ölü ayar** — editörde var ama komut sütununa başlık verilmez | | `SendOnlyChangedFormValuesUpdate` | `false` | Update'te yalnızca değişen alanları gönder | | `AddPageUrl` | — | Yeni kayıt popup yerine bu adreste açılır (`window.open`; PWA'da aynı sekme) | | `AddDialogName` | — | Yeni kayıt kayıtlı bir diyalogda açılır (`CommandColumnDto.DialogName` ile aynı liste). `AddPageUrl` ile birlikte verilirse **diyalog önceliklidir**; diyalog kapanınca liste yenilenir | | `AddDialogParameters` | — | `AddDialogName` diyaloguna geçilen sabit parametreler. Tipi **string**'dir: nesne değil, JSON metni yazılır (`"{\"id\":\"\"}"`); istemci `safeJsonParse` ile açar, bozuksa `{}` geçer. Toolbar'da satır bağlamı olmadığı için `@Kolon` token'i kullanılmaz | | `Popup` | — | Aşağıdaki alt nesne | `Popup` (`GridEditingPopupDto`): `Title`, `ShowTitle` (`true`), `Width` (`700`), `Height` (`500`), `FullScreen` (`false`), `HideOnOutsideClick` (`true`), `ResizeEnabled` (`true`), `DragEnabled` (`true`), `RestorePosition` (`true`), `Position` (`center` \| `top` \| `bottom` \| `left` \| `right`). - `Title` dil anahtarıdır (`::` eklenip çevrilir; önüne ✚/🖊️ konur). - `Height` sabit yükseklik değil **azami yüksekliktir** (`maxHeight`; boşsa `90vh`), popup içeriğe göre büyür. - Mobilde (`<768px`, `pointer:coarse` ya da `hover:none`) popup tam genişlik/tam ekran açılır, form tek kolona iner; tam ekran aç/kapa düğmesi her zaman vardır. - `DragEnabled`, `RestorePosition`, `Position` çalışır ama ListForm editöründe alanı yoktur — seed ile yazılır. ### 3.2 `EditingFormJson` (`EditingFormDto[]`) Düzenleme formundaki grupların listesi. Alanların hangi gruba düştüğü `ListFormField` üzerindeki `EditGroupOrderNo` / `EditOrderNo` ile belirlenir. | Anahtar | Varsayılan | Anlam | | --- | --- | --- | | `Order` | `1` | Grubun sırası (`EditGroupOrderNo` ile eşleşir) | | `ItemType` | `"group"` | `group` (kullanılan) \| `tabbed` \| `simple` \| `empty` \| `button` — bütün `tabbed` gruplar ilk `tabbed` grubun yerinde tek sekme şeridi olur (liste popup'ı ve form sayfası aynı) | | `Caption` | — | Grup başlığı (dil anahtarı) | | `ColCount` | `2` | Grup içi kolon sayısı | | `ColSpan` | `2` | Grubun formda kapladığı kolon | Wizard bu listeyi `Groups` bloğundan üretir; anahtar alan ve `IncludeInEditingForm: false` olanlar dışarıda kalır. --- ## 4. Yazma komutları ve kancalar ### 4.1 Komutlar | Kolon | Ne zaman | | --- | --- | | `InsertCommand` / `UpdateCommand` / `DeleteCommand` | Boşsa platform komutu kendi üretir; doluysa **bu SQL** çalışır | | `InsertBeforeCommand` / `InsertAfterCommand` | Insert'ten önce/sonra | | `UpdateBeforeCommand` / `UpdateAfterCommand` | Update'ten önce/sonra | | `DeleteBeforeCommand` / `DeleteAfterCommand` | Delete'ten önce/sonra | **Kancalar, kod yazmadan iş mantığı çalıştırmanın yeridir**: stok düşme, hareket kaydı, durum güncelleme, log, bildirim kuyruğuna satır ekleme. Karmaşıklaşınca gövdeyi bir stored procedure'e taşı ve kancadan onu çağır (`EXEC Wms_S_AfterOrderInsert @Id, @LastModifierId`). - Kancalar ana komutla **aynı parametre sözlüğünü** kullanır. - Insert'in ana komutu `ExecuteScalar` ile çalışır (SQL Server `OUTPUT Inserted.{Key}`, PG `RETURNING`); özel `InsertCommand` anahtarı döndürmezse istemci yeni kaydın anahtarını alamaz (detaya gitme, alt form). - Otomatik Update'te istekte `TenantId` varsa `WHERE`'e `AND TenantId` eklenir; otomatik Delete'te **bütün parametreler** `WHERE` koşuludur. - **Kopyala** (`list-form-data/duplicate`) `InsertCommand` + Insert kancaları + `InsertFieldsDefaultValueJson` ile çalışır. - Yazmadan sonra ekranın seed senkronu çalışır (`SeedSync*`, `lowcode` §9.15). ### 4.2 Komut içinde kullanılabilen anahtarlar Yazma komutlarında ve kancalarında **metin değiştirme yoktur** (`DefaultValueHelper` çalışmaz); yalnızca Dapper parametreleri bağlanır: | Parametre | Kaynağı | | --- | --- | | `@{FieldName}` | İstekte gelen alan — Insert/Update'te yalnızca alan seviyesinde `C`/`U` yetkisi olanlar (Delete'te yetki süzgeci yok) | | `@{FieldName}` | `*FieldsDefaultValueJson` öğesi — adı token değil **alan adıdır**: `@CreatorId`, `@CreationTime` (`@USERID` değil) | | `@TenantId` | `IsTenant` açıksa | | `@{KeyFieldName}` | Yalnızca **otomatik üretilen** Update/Delete'te eklenir. Özel `UpdateCommand`/`DeleteCommand`'da anahtar parametresi gelmez; wizard bunu `DeleteFieldsDefaultValueJson`'a `{FieldName:"Id", Value:"@ID", CustomValueType:2}` yazarak çözer (`DefaultDeleteCommand` → `… WHERE "Id" IN @Id`) | Bu yüzden `@USERID`, `@USERNAME`, `@ROLES`, `@ID` komut metninde **çalışmaz** ("Must declare scalar variable"); kullanıcıya/zamana bağlı değer gerekiyorsa önce ilgili `*FieldsDefaultValueJson`'a alan adıyla yazılır (`{FieldName:"LastModifierId", Value:"@USERID", CustomValueType:2}`), komutta `@LastModifierId` kullanılır (`lowcode` §4.3.5). **String birleştirme ile sorgu kurma.** **Metin değiştirmenin çalıştığı yerler** (`DefaultValueHelper.GetDefaultValue`; `@USERID`, `@USERNAME`, `@ROLES`, `@DATE`, `@NOW`, `@DAY`, `@MONTH`, `@YEAR`, `@TENANTID`): `SelectCommand`, `DefaultFilter`, kullanıcı `ServerWhere` özelleştirmesi, widget `SqlQuery`, `LookupQuery`, `DbQuery` varsayılanının `SqlQuery`'si, Custom Endpoint parametre varsayılanları, rapor lookup'ları. Host bağlamında `= '@TENANTID'` → `IS NULL`, yalın `@TENANTID` → `NULL` olur; `@ROLES` `','` ile birleşir (`IN ('@ROLES')` kalıbı). `@ID`, `@NEWID`, `@SELECTED_IDS`, `@AUTONUMBER` metinde çözülmez; `@BRANCHID`/`@LANG` diye token yoktur. Çalışmadığı yerler: yazma komutları ve kancaları, `ServerJoin` özelleştirmesi, `ExtraFilterJson` (istemci filtresi olarak parametreleşir), grafik özet sorgusu. **Örtük filtreler** (token değil, sorguya kendiliğinden eklenir): `IsTenant` → `TenantId = '{id}'`/`IS NULL`; `IsBranch` → `BranchId IN (kullanıcının şubeleri)` — kullanıcının şubesi yoksa hiç satır gelmez; `IsOrganizationUnit` → `OrganizationUnitId IN (birim + alt birimler)`; `ApprovalIsFilterUserName` → onaycı filtresi. Widget SQL'i bu filtrelerden **geçmez** (kiracı koşulunu kendisi yazar). Yazma tarafında şube/birim ataması yoktur — `BranchId` varsayılanla ya da formdan gelir. **Kullanıcının şubeleri ve şubedeki varsayılan depo/konum:** kullanıcı–şube ataması `Sas_T_BranchUsers`'tadır ve Kullanıcılar › detay › Yetkiler › Şube Yönetimi'nden yapılır. Atanan her şubede kullanıcının **varsayılan deposu** (`DefaultWarehouseId`, şubenin aktif deposu) ve **varsayılan konumu** (`DefaultLocationId`, o deponun aktif iç konumu) seçilir; şubenin deposu, deponun iç konumu varsa ikisi zorunludur (ekran ve `PlatformIdentityAppService.UpdatePermissionAsync`). Seçenekleri Envanter'in `Inv_T_Warehouse`/`Inv_T_Location` tablolarından servis okur; modül yoksa seçenek boş kalır. Bir ekranda "şube seçilince kullanıcının varsayılan deposu/konumu gelsin" için: `IsBranch: true`, şube lookup'ı `Sas_T_BranchUsers` ile `UserId = '@USERID'` üzerinden süzülüp `DefaultWarehouseId`/`DefaultLocationId` kolonlarını döndürür, şube alanının `EditorScript`'i `copy("DefaultWarehouseId", "WarehouseId")` (+ konum) ile yazar; yeni kaydın şube/depo/konum varsayılanı `DbQuery` ile aynı tablodan okunur. Örnek: `App.Prc.MyMaterialRequest`, `App.Prc.PurchaseRfq`, `App.Prc.PurchaseOrder`. ### 4.3 `*ServiceAddress` `InsertServiceAddress` / `UpdateServiceAddress` / `DeleteServiceAddress` varsayılan olarak `list-form-data/insert|update|delete` uçlarıdır. Yazma işini bir Custom Endpoint ya da Dynamic Service'e devretmek istiyorsan bu alanı o ucun adresiyle değiştirirsin — ekranın geri kalanı (form, doğrulama, yetki) aynı kalır. - Çağrı **her zaman POST**'tur ve adres `'/api/app/' + adres` olarak kurulur (baştaki `/` silinir); `http…` ile başlayan mutlak adres de önek alır ve çalışmaz — dış servis bir Custom Endpoint / Dynamic Service arkasına konur. - Gövdeler: insert `{data, listFormCode}` · update `{keys:[key], data, listFormCode}` (yalnızca değişen alanlar) · delete `{keys:[key], listFormCode}`. - **Adres boşsa işlem sessizce yapılmaz**; TodoBoard ekle/güncelle/sil düğmeleri kapanır, `UpdateServiceAddress` boşsa iş akışı toolbar'ı tamamen gizlenir. Salt okunur ekran için adresleri boşaltmak yerine `EditingOptionJson`'daki izin bayraklarını kapat. - Yetki ucun kendisinde (Create/Update/Delete) denetlenir; bağlantı `DataSourceManager.GetDataSourceAsync(IsTenant, DataSourceCode)` ile çözülür (`DataSourceType` `1` Mssql · `2` Postgresql). **Çok değerli alanlar bu iki yolda farklı davranır.** Varsayılan uçta payload bir sözlüktür (`data: {...}`) ve `dxTagBox` dizisini `QueryHelper.GetFormattedValue` `|` ile birleştirir. Tipli bir DTO alan uçta (`list-form-dynamic-api/...` gibi) böyle bir adım yoktur: dizi `string` property'ye bağlanamaz ve istek `400` ile düşer. Çözüm, DTO alanına `[JsonConverter(typeof(MultiValueStringJsonConverter))]` eklemektir — `Modules` alanında uygulanmış örneği `CreateUpdateTenantInput` / `CreateTenantFromOrderInput` taşır. Yalnızca **girdi** DTO'larına eklenir; okuma DTO'larında (`SettingDefinitionDto.Providers`, `PlatformPermissionGrantInfoDto.Modules`) karşılığı yoktur çünkü o ekranlar varsayılan SQL yolundan kaydeder. --- ## 5. Ekran seviyesindeki diğer bloklar ### 5.1 `PermissionJson` (`PermissionCrudDto`) Tek harfli anahtarlar: `R` (read), `C` (create), `U` (update), `D` (delete), `E` (export), `I` (import), `N` (note). Değerler **yetki adıdır**, boolean değil: ```json { "R": "App.Mrp.Orders", "C": "App.Mrp.Orders.Create", "U": "App.Mrp.Orders.Update", "D": "App.Mrp.Orders.Delete", "E": "App.Mrp.Orders.Export", "I": "App.Mrp.Orders.Import", "N": "App.Mrp.Orders.Note" } ``` Alan seviyesindeki karşılığı `ListFormField.PermissionJson`'dır ve **şeması farklıdır** (`ListFormFieldPermissionDto`) — ekran seviyesindeki yedi harfli sözleşmeyi buraya kopyalama: | Anahtar | Tip | Anlam | | --- | --- | --- | | `Deny` | bool | `true` ise alan **hiç dönmez**; diğer anahtarlara bakılmaz | | `C` · `R` · `U` | string | Yaratma / okuma / güncelleme yetkisinin **adı**. Boşsa ekranın kendi Create/Read/Update yetkisi kullanılır | | `E` | bool | Alan dışa aktarımda yer alsın mı — **yazılmazsa `false`**: `PermissionJson`'ı olmayan alan dışa aktarılmaz | | `I` | bool | Yalnızca istemcide: içe aktarma şablonunun kolonlarını seçer; sunucu yok sayar | ```json { "C": "App.Mrp.Orders.Create", "R": "App.Mrp.Orders", "U": "App.Mrp.Orders.Update", "E": true, "I": true, "Deny": false } ``` `ListFormField` üzerindeki `CanRead` / `CanCreate` / `CanUpdate` / `CanExport` kolonları **veritabanında yoktur** (`[NotMapped]`); `ListFormFieldManager.SetPermissionsAsync` bu JSON'u oturumdaki yetkilerle karşılaştırarak istek başına hesaplar. Üçü de `false` çıkan alan metadata'dan tamamen düşer — yani okuma yetkisi olmayan bir kolon sorguya bile girmez. `C`/`U` yetkisi olmayan alan sunucuda insert/update verisinden atılır ve formda **salt okunur** görünür. Wizard varsayılan olarak `DefaultFieldPermissionJson(permissionName)` üretir. ### 5.2 `SubFormsJson` (`SubFormDto[]`) | Anahtar | Anlam | | --- | --- | | `TabType` | Sekmenin görünümü — `ListFormTabTypeEnum`: `List` \| `Card` \| `Tree` \| `Gantt` \| `Scheduler` \| `Todo` \| `Form` \| `Chart` \| `Pivot`. `Form` ana kayda bağlı **tek** kaydı gösterir: kayıt yokken "Yeni" düğmesi (hedef ekranda `AllowAdding` açıksa), kayıt varken yalnızca düzenleme; ikinci kayıt açılmaz | | `TabTitle` | Dil anahtarı | | `Code` | Hedef `ListFormCode` | | `IsRefresh` | `true` ise alt sekmede satır eklenip/değiştirilip/silindiğinde **ana form kaydı** yeniden çekilir (ana kayıt değişince alt sekme zaten her zaman yenilenir) — alt satırlardan hesaplanan başlık toplamı için açılır. MCP `deploy_wizard` yeni alt form bağlantısını `false` yazar; var olan bağlantıda önceki değer korunur | | `Relation[]` | `ParentFieldName` → `ChildFieldName` + `DbType` (`System.Data.DbType`, **sayı**). İstemci yalnızca değeri dolu **ilk** satırı kullanır — bileşik anahtar desteklenmez | Alt formun kendisi de bir ListForm'dur: `IsSubForm = true` olmalı ve genellikle `CreateMenu: false` ile menüsüz üretilir (`lowcode` §4.2). `TabType` hedefin `LayoutJson` bayrağına bakmaz (Grid/Card/ Pivot bayrak kapalı olsa da çizilir); Tree/Gantt/Scheduler/Todo hedefte ilgili `*Expr` alanlarını ister. - **`Form` sekmesi** kaydı ilişkiyle (`ChildFieldName = ana alanın değeri`) yükler; düzenlemede anahtar yüklenen kayıttan okunur. Yeni kayıtta ilişki alanı ana kaydın değeriyle dolu ve kilitli açılır. - **`Chart` sekmesi** ana ekranın grafik tanımını kullanır ve ilişkiyle süzülmez. - Form sayfasında alt formlar görüntüle/düzenle modunda çizilir, yeni kayıtta çizilmez. Adres `#{AltFormKodu}` ile açılırsa o sekme seçilir; sekme değişince hash yazılır. ### 5.3 `WidgetsJson` (`WidgetEditDto[]`) Ekranın üstündeki KPI şeridi. **Sözleşmesi sezgisel değildir, dikkatle oku:** | Anahtar | Ne yazılır | | --- | --- | | `SqlQuery` | Kartları besleyen sorgu. **Zorunludur** — boş olan widget hiç çizilmez | | `Title` · `SubTitle` · `Value` · `Color` · `Icon` · `OnClick` | **Sorgu sonucundaki kolon adları** — sabit metin değil. `OnClick` kolonunun **değeri JS olarak `eval` edilir** (adres değil): `'window.open(''/admin/list/X'')' AS Tikla`. Sunucu her satırda bu adlı kolonu okur; kolon yoksa boş string döner. `Title`/`SubTitle` kolonunun **değeri** ekranda dil anahtarı olarak çözülür (`WidgetGroup`, `::` öneki eklenerek); karşılığı olmayan metin aynen basılır, bu yüzden sorguya dil anahtarı yazılır | | `ColSpan` · `ColGap` · `ClassName` | Şeritteki yerleşim (widget başına, karta değil): `ColSpan` 12 kolonluk ızgarada kart genişliği (mobilde 6'ya zorlanır), `ColGap` Tailwind `gap-{ColGap}` eki (`3` önerilir; `16` = 4rem) | | `ValueClassName` | Değerin CSS sınıfı, varsayılan `text-3xl` | | `IsActive` | **Varsayılanı `false`'tur** — yazılmazsa sorgu hiç çalışmaz ve şerit boş kalır; her widget'a `true` yaz | **Bir widget, sorgunun döndürdüğü her satır için bir kart çizer.** Yani tek bir widget kaydıyla "Bekleyen / Onaylı / Reddedilen" üçlüsü tek sorgudan üretilebilir: ```sql SELECT 'Bekleyen' AS Baslik, COUNT(*) AS Deger, 'amber' AS Renk, 'FaClock' AS Ikon FROM Mrp_T_Order WHERE Status = 1 UNION ALL SELECT 'Onaylı', COUNT(*), 'green', 'FaCheck' FROM Mrp_T_Order WHERE Status = 2 ``` ```json { "Title": "Baslik", "Value": "Deger", "Color": "Renk", "Icon": "Ikon", "ColSpan": 4, "IsActive": true } ``` Sorgu ekranın bağlantısında (`IsTenant` + `DataSourceCode` ile çözülen, `lowcode` §9.3.3), **olduğu gibi** çalıştırılır: - Ekranın otomatik tenant/şube/OU filtresi **uygulanmaz**; kırılım sorguya yazılır. - Sorgu `DefaultFilter` ile aynı token çözümünden geçer (`DefaultValueHelper.GetDefaultValue`): `@USERID`, `@USERNAME`, `@DATE`, `@NOW` ve `@TENANTID` metin olarak yerine konur. Kiracı tablosunda (`IsTenant: true`) her karta `"TenantId" = '@TENANTID'` yaz; host'ta bu `"TenantId" IS NULL`'a döner, yani kartlar liste ekranıyla aynı kümeyi sayar. Yazılmazsa ortak veritabanında kartlar bütün kiracıların toplamını gösterir. - `IsDeleted` koşulu widget sorgusuna yazılmaz (platform varsayılanı). - Wizard / MCP ile üretilen List ekranı sorulmadan en az 3 kartla gelir; kart seçimi, istisnalar ve metin/renk kuralı: `lowcode` §4.6.1. - Seeder'da şerit `DefaultWidgetsJson(tablo, listFormName, tenant, params WidgetCard[])` ile kurulur (`ListFormSeeder_DefaultJsons`): kart başına `Title` (dil anahtarı), renk, ikon, ek koşul ve isteğe bağlı `Value` ifadesi; alt başlık ekranın adıdır, değer tip çakışmasın diye metne çevrilir. Gruplamalı sorgu için `DefaultWidgetsJson(sql, kartSayısı)`. Genel kart başlıkları `App.Widget.*` dil anahtarlarındadır. - Kartlar GridDto ile gelir; Grid, Tree, Card, Gantt ve Scheduler görünümünde Yenile düğmesi ve satır ekleme/güncelleme/silme kartları yeniden çeker (`useListFormWidgets`). Yalnızca widget listesi değişir, GridDto değiştirilmez — görünüm durumu (sayfa, seçim) korunur. Kart sorgusu ekranın filtresine bağlı olmadığı için filtre değişikliği kartları yenilemez. Pivot'ta şerit çizilir ama yenilenmez. - Kart sorgusu alt form sekmesinde `Relation` ile süzülmez; bu yüzden alt form olarak üretilen ekrana widget yazılmaz (menüsüz ya da menülü). Menüsüz (`CreateMenu: false`) wizard'ın `Widgets`'ı deploy'da ayrıca düşürülür; ekran alt form olarak (`SubForms` sekmesi, `isSubForm`) açıldığında şerit çizilmez ve yenilenmez. ### 5.4 `LayoutJson` (`LayoutDto`) | Anahtar | Varsayılan | | --- | --- | | `Grid` · `Card` · `Pivot` · `Chart` | `true` | | `Tree` · `Gantt` · `Scheduler` · `Todo` | `false` | | `DefaultLayout` | `"grid"` | Wizard'daki görünüm bayraklarının karşılığıdır. Varsayılan **dörtlüdür**: `Grid`, `Card`, `Pivot`, `Chart` her liste ekranında açık gelir; üretilen ekranda bunlardan biri yalnızca istek onu açıkça dışarıda bıraktığında kapatılır (`lowcode` §9.8). Aynı varsayılan üç yerde tutulur ve birlikte değişir: UI wizard başlangıç değerleri (`Wizard.tsx`), `ListFormWizardDto` ve `LayoutDto` (+ `WizardConsts.DefaultLayoutJson`). Tree/Gantt/Scheduler/Todo zorunlu `*Expr` alanı ister (§7); bayrak açık olsa bile alanlar boşsa istemci görünümü göstermez (`List.tsx` `isLayoutValid`) ve seeder o görünümün JSON'unu yazmaz. `DefaultLayout`, `true` olan bir görünümü göstermelidir. - **`Grid` bayrağı ölüdür**: Grid düğmesi her zaman çizilir, kapatılamaz. - `Card` bayrağı yalnız düğmeyi gizler; `DefaultLayout: "card"` bayrak kapalıyken de açılır. - Düğme koşulu `isLayoutValid`'den gevşektir: Tree düğmesi yalnız `ParentIdExpr`, Gantt düğmesi `ParentIdExpr` + `TitleExpr` ister. Geçersiz `DefaultLayout` ya da kayıtlı görünüm `grid`'e düşer. - Kullanıcının seçtiği görünüm sunucuda değil tarayıcının localStorage'ında (`admin.lists.states`) durur. ### 5.5 `CustomJsSourcesJson` / `CustomStyleSourcesJson` Her ikisi de **string dizisidir**. Bir eleman `http` ya da `/` ile başlıyorsa dosya olarak sayfaya eklenir (`