@appaflytech/zeyrek-ai-mcp 0.0.23 → 0.0.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/.env.example +15 -0
  2. package/dist/config.js +9 -0
  3. package/dist/index.js +2 -0
  4. package/dist/knowledge/componentCatalog.js +1 -1
  5. package/dist/knowledge/mobileComponentCatalog.js +231 -58
  6. package/dist/knowledge/mobileComponentEvents.js +118 -3
  7. package/dist/knowledge/normalize.js +16 -6
  8. package/dist/knowledge/platform/lint.js +110 -3
  9. package/dist/knowledge/platform/mobileRules.js +64 -8
  10. package/dist/knowledge/rules.js +56 -7
  11. package/dist/knowledge/styleResolve.js +49 -3
  12. package/dist/knowledge/styleResolveNative.js +104 -4
  13. package/dist/observability/trace.js +1 -1
  14. package/dist/platform/argumentAliases.js +128 -0
  15. package/dist/platform/bodyFile.js +103 -0
  16. package/dist/platform/capture/capture.js +29 -5
  17. package/dist/platform/capture/console-log.js +80 -0
  18. package/dist/platform/capture/index.js +1 -0
  19. package/dist/platform/diagnostics.js +10 -4
  20. package/dist/platform/http.js +23 -7
  21. package/dist/platform/responses.js +6 -2
  22. package/dist/platform/schemaReadMemory.js +60 -0
  23. package/dist/platform/secrets.js +9 -0
  24. package/dist/platform/strictArguments.js +68 -12
  25. package/dist/platform/tokenVault.js +187 -4
  26. package/dist/resources/guides/expressions.md +1 -1
  27. package/dist/resources/guides/microservice.md +9 -2
  28. package/dist/resources/guides/middleware.md +5 -1
  29. package/dist/resources/guides/mobile.md +150 -14
  30. package/dist/resources/guides/page.md +146 -39
  31. package/dist/resources/guides/theme.md +4 -1
  32. package/dist/resources/guides/workflow.md +7 -1
  33. package/dist/resources/schema-fingerprint.json +2 -2
  34. package/dist/server.js +1 -1
  35. package/dist/tools/datasources.js +1 -1
  36. package/dist/tools/designSchemaPatch.js +4 -3
  37. package/dist/tools/externalComponents.js +1 -1
  38. package/dist/tools/flows.js +70 -4
  39. package/dist/tools/layouts.js +5 -5
  40. package/dist/tools/microservices.js +26 -10
  41. package/dist/tools/pages.js +192 -36
  42. package/dist/tools/platformInfo.js +4 -1
  43. package/dist/tools/rows.js +7 -1
  44. package/dist/tools/schemaOps.js +1 -1
  45. package/dist/tools/screenWrite.js +4 -3
  46. package/dist/tools/screens.js +81 -0
  47. package/package.json +1 -1
@@ -57,7 +57,11 @@ cevabı adımın ifadesinde gizlidir. Ayrıntı ve yazım ölçütü: `platform_
57
57
  Gateway `set-user` adımı olmadan `user` okuyan middleware'i **MW-003** ile uyarır.
58
58
  4. **`input` YOKTUR.** Middleware girdisini HTTP bağlamından okur: `cookie`, `header`,
59
59
  `queryString`, `body`, `triggerSource` (hangi microservice/sayfa tetikledi: type, name,
60
- slug, roles, impersonatedUserId). Hazır fonksiyonlar: `system.validateToken(...)`,
60
+ slug, roles, impersonatedUserId). `impersonatedUserId` ve start adımının `impersonation`
61
+ dalı yalnız Studio önizlemesindeki doğrulanmış platform kullanıcısı için dolar; anonim ya da
62
+ yetkisiz istekte başlık platformda silinir. `impersonation` dalı bağlı değilse start
63
+ `success`'e düşer — uygulamanın kendi auth yolu koşar (ZM-22, 2026-09-26). Hazır
64
+ fonksiyonlar: `system.validateToken(...)`,
61
65
  `system.generateToken(...)`, `crypto.JWT.encode/validate`, `Authorized(user.roles)`,
62
66
  `request.url.searchParams`, `GenerateGuid()`, `UtcNow()`, `system.getPageUrl(slug)` —
63
67
  **tam sözlük ve gerçek örnekler: `platform_guide('expressions')`**.
@@ -37,7 +37,7 @@ Mobil uygulamanın bileşen kataloğu ayrıdır (backend satırları `(slug, typ
37
37
  mobil farkları: düşen prop, değişen etkileşim, mobile özgü prop notları) ve `mobileNotes` (bütün
38
38
  mobil bileşenlere geçen notlar). Web'de karşılığı olmayan bileşende (`app-bar`, `safe-area`,
39
39
  `scroll-view`, `choice-chips`, `tooltip`, `markdown`, `calendar`, `stack`, `list-item`, `fab`, `timer`,
40
- `audio`, `lottie`, `webview`, `map`) bilgi yalnız `mobileKnowledge`'tadır. Olay yükleri
40
+ `audio`, `lottie`, `webview`, `map`, `grouped-list`, `floating-container`) bilgi yalnız `mobileKnowledge`'tadır. Olay yükleri
41
41
  (`events[].payload`) önce mobile özgü sözleşmeden okunur (`app-bar.clickAction`, `repeater.swipe`,
42
42
  `choice-chips.change`…), yoksa web'inkinden.
43
43
 
@@ -46,7 +46,7 @@ mobil bileşenlere geçen notlar). Web'de karşılığı olmayan bileşende (`ap
46
46
  olup mobilde henüz olmayan için ne zaman geleceğini söyler (Dalga 2, Faz 3).
47
47
  - **Katalog A** (FlutterFlow paritesi, 2026-09-24) on bileşen ekledi: `badge`, `avatar`, `progress`,
48
48
  `radio`, `checkboxgroup`, `choice-chips`, `collapsible-container`, `slider`, `tabs`, `tooltip`;
49
- `button` mobilde `secondary`/`ghost`/`link` varyantlarını ve `icon` boyunu alır. Tam liste
49
+ `button` mobilde `secondary`/`ghost`/`link`/`tonal` varyantlarını ve `icon` boyunu alır. Tam liste
50
50
  `component_list`'tir.
51
51
  - **Katalog B** (FlutterFlow paritesi) önce dört prop'la başladı; hepsi YALNIZ mobil şemadadır:
52
52
  `scroll-view.stickyHeader` (ilk çocuk dikey kaydırmada üstte kalır), `numberinput.variant: stepper`
@@ -90,12 +90,26 @@ mobil bileşenlere geçen notlar). Web'de karşılığı olmayan bileşende (`ap
90
90
  önizlemede tarayıcının seçicisi açılır; değer ve `change` yükü web ile aynıdır. Sistem seçicisi tek tek
91
91
  saati kapatamadığı için `minTime`/`maxTime`/`disabledTimes` dışındaki seçim izinli en yakın saate yaslanır;
92
92
  `datetimepicker` tek kutudur (`timePlaceholder` yok). `page_screenshot` `set` kökte değil
93
- `<elementId>__input`ta, HTML biçimiyle çalışır (`2026-08-01`, `09:30`, `2026-08-01T09:30`). `fileInput` ve `filebutton` de web ikizidir:
94
- değer web gibi base64 veri URL'sidir (dosya yüklenmez), tür/boyut/adet kapısı ve uyarılar web kuralı.
93
+ `<elementId>__input`ta, HTML biçimiyle çalışır (`2026-08-01`, `09:30`, `2026-08-01T09:30`). Tasarım takvim
94
+ istiyorsa (Pazartesi başlangıcı, "Bugün / Dün" çipleri, `24.09.2026`): `datepicker` `presentation: calendar` +
95
+ `quickPicks` + `displayFormat`; aralık için `daterangepicker` (web ikizi, `valueFrom`/`valueTo`, hep takvim
96
+ sayfası). `fileInput` ve `filebutton` de web ikizidir:
97
+ değer web gibi base64 veri URL'sidir — bileşen kendisi yüklemez, kayda Table microservice'in FILE
98
+ parametresiyle yazılır (§Dosya ve fotoğraf); tür/boyut/adet kapısı ve uyarılar web kuralı.
95
99
  Seçiciyi mobile özgü `source` seçer — `files` (belge seçici), `library` (fotoğraf arşivi), `camera`,
96
- `ask` (her seferinde sorar; `filebutton`'ın varsayılanı); fotoğraflar `quality` ile sıkıştırılır ve
97
- iOS'ta HEIC JPEG'e çevrilir. Önizlemede tarayıcının dosya penceresi açılır (kanvasta alan kilitli);
98
- kırpma yoktur.
100
+ `ask` (her seferinde sorar; `filebutton`'ın varsayılanı); fotoğraflar `maxImageDimension` (varsayılan
101
+ 1600 px uzun kenar) ile küçültülüp `quality` ile sıkıştırılır ve iOS'ta HEIC JPEG'e çevrilir.
102
+ Önizlemede tarayıcının dosya penceresi açılır (kanvasta alan kilitli); kırpma yoktur.
103
+ - **Liste ve sabit konum** (2026-09-26): `grouped-list` (başlıklı gruplu liste — §Güne göre görev
104
+ listesi) ve `floating-container` (ekranın 9 noktasından birine sabit kap — §Ekrana sabit içerik)
105
+ mobile özgüdür. `scroll-view`'de ilk çocuk dışındaki doğrudan çocuklar `states.isSticky: true` ile
106
+ kaydırırken üstte kalır (Studio panelinde alanı yok; şemaya MCP yazar).
107
+ - **Faz 10** (FlutterFlow D listesi): `datatable` web ikizidir — kolon biçimi, sıralama ve sayfa hesabı web'in
108
+ modülünden, akış sözleşmesi aynı (sıralama/sayfa değişince `sortField`/`sortType`/`offset` düğüme yazılır ve
109
+ `mount` yeniden koşar; satır eylemi `selectedRow`u yazıp eylemin akışını koşturur). Mobile özgü `variant`:
110
+ `table` (varsayılan, yatay kaydırılan tablo; başlığa dokunuş `__sort-<kolon>`) ya da `cards` (satır başına
111
+ kart). Eylemler `__action-<satır>-<sıra>`, `dropdown` kipinde `__more-<satır>` → alttan eylem sayfası;
112
+ sayfa çubuğu `__page-prev`/`__page-next`. Filtre düzenleyicisi, özet satırı ve sabit eylem sütunu yok.
99
113
  - Web'e özgü bileşenlerin mobil karşılığı:
100
114
 
101
115
  | Web | Mobilde |
@@ -109,7 +123,121 @@ mobil bileşenlere geçen notlar). Web'de karşılığı olmayan bileşende (`ap
109
123
  | `fileUpload` | `fileInput` (alan) ya da `filebutton` (düğme) — sürükle-bırak yok |
110
124
 
111
125
  **Harici bileşen mobilde yoktur** (`MOB-006`): protokol v1 tarayıcı `.mjs` paketidir;
112
- `external_component_*` araçları mobil uygulamada platforma gitmez.
126
+ `external_component_*` araçları mobil uygulamada platforma gitmez. **Geçici yol `html`** (2026-09-28):
127
+ katalogda karşılığı olmayan küçük görünüm (özel rozet, SVG, biçimli metin) `html` ile konur — içerik
128
+ kendi korumalı belgesinde çizilir (uygulamanın çerezine/jetonuna ulaşamaz), tema `:root` değişkeni
129
+ olarak gelir (`hsl(var(--primary))`, `var(--ng-…)`), boy içeriğe göre ayarlanır, bağlantı sistem
130
+ tarayıcısında açılır. Akışla iki yön: belgede `fireEvent(key, data)` → `actions` satırının akışı,
131
+ `window.ngApps.emit(name, data)` → `message` olayı (`event.name`/`event.data`); akışın `message`
132
+ prop'una yazdığı değer belgede `window.ngApps.on(name, fn)` ile alınır. Aynı köprü `webview`de de var.
133
+ - **Etkileşim yüzeyleri** (2026-09-27): bileşik kart `container` + `click` ile kurulur (mobile-only olay;
134
+ bağlıysa kap runtime'da basılır, içteki düğme kabın `click`ini tetiklemez) — `card` çocuk almaz.
135
+ `button` ikonu (`icon` + `iconPosition: left|right|top`) ve rozeti (`badgeValue`, `'•'` nokta) çizer;
136
+ `iconPosition: top` + `heightType: fixed` + `variant: tonal` dikey hızlı eylem karosudur. `size: icon`
137
+ yalnız glif çizer: `ariaLabel` verin (MOB-013). `icon` `click`/`href` ile basılır, `isDisabled` soldurur.
138
+ `modal`/`drawer` `actions` düğme satırı çizer (`action` olayı, `currentItem`; `type: submit` formu
139
+ gönderir). Dış bağlantı (`href`) sistem tarayıcısında açılır; uygulama içi yol akışın `redirect`'idir.
140
+
141
+ ## Dosya ve fotoğraf
142
+
143
+ "Fotoğraf çek → kaydet → listede göster" üç parçadır; yükleme ucu yoktur, dosya kaydın yazıldığı
144
+ istekle gider.
145
+
146
+ 1. **Tablo.** Entity'ye `dbType: 'FILE'` + `commonType: 'file'` alanı (ör. `foto`; tekil dosya,
147
+ `relationId` verilmez — `platform_guide('entity')`).
148
+ 2. **Yazma ucu.** Table microservice `operation: 'create'` (ya da `upsert`); `fields`'ta alanı
149
+ `{ fieldSlug: 'foto' }` ile listeleyin. Uç gövdede alanın slug'ıyla `type: 'file'` bir parametre
150
+ bekler ve değeri `{ type, value, fileName }` biçiminde okur (`TableFileParameter`): `type: 'file'`
151
+ base64'ü depoya yazar ve kayda dosya kimliğini koyar, `type: 'ref'` var olan dosyayı (`value` =
152
+ dosya id'si) bağlar, `value: null` alanı boşaltır. Dosya alanının DEĞERİ bu biçimdedir — dönüştürmeyin.
153
+ 3. **Form sayfası.** `fileInput` (`source: 'camera' | 'library' | 'ask'`) ya da `filebutton` + kaydet
154
+ düğmesi. Düğmenin akışında `microservice` adımı, parametre alanın değerini olduğu gibi taşır:
155
+
156
+ ```json
157
+ { "name": "foto", "type": "file", "source": "body",
158
+ "value": { "type": "property", "ref": { "element": "foto_in", "property": "value" } } }
159
+ ```
160
+
161
+ Base64 gövdeyle gider: fotoğraflar istemcide `maxImageDimension` (1600 px) ile küçültülür,
162
+ `maxFileSizeMb` mobilde varsayılan 10'dur — ikisini de sıfırlamayın. Kamera izni reddedilirse alan
163
+ Ayarlar'a yönlendiren bir uyarı gösterir.
164
+ 4. **Liste.** `get` ucu FILE alanını `{ id, type: 'ref', value: <dosya id>, absolute_path: <url>,
165
+ fileName }` olarak döndürür. `image.src`, `avatar.src`, `card.image`, `list-item.leadingImage`'a
166
+ **nesnenin kendisini** verin: `{ "type": "code", "ref": "{{ item.foto }}" }` (`.absolute_path` da
167
+ çalışır). `{{ item.foto.value }}` dosya **kimliğidir** (GUID), görsel çizilmez — `MOB-011` uyarır.
168
+ Gateway alan tipini şemadan bilemediği için uyarı metinseldir: `.value` ile biten en az üç parçalı
169
+ yol (`urlInput.value` gibi bileşen değeri ve `fileInput1.value.value` uyarı almaz).
170
+
171
+ Workflow'un dosya yükleme adımı yalnız `{ id }` döndürür (URL yok); görsel adresi kaydı okuyan uçtan
172
+ gelir. `page_screenshot` ile listede görseli doğrulayın.
173
+
174
+ ## Güne göre görev listesi
175
+
176
+ Başlıklar altında gruplanmış liste (güne, odaya, kategoriye göre) **tek `grouped-list`** ile kurulur.
177
+ İç içe repeater `PAGE-013` ile reddedilir; `textarea` hilesi de gerekmez — liste düz satır dizisidir,
178
+ bölümleri bileşen render anında çıkarır.
179
+
180
+ 1. **Veri.** Düz satırlar (ör. `q_tasks` sorgusu: `title`, `dueDate`, `done`). Sıralama sorguda
181
+ değil bileşende: `sectionOrder` `asc` (varsayılan) · `desc` · `asis`.
182
+ 2. **Düğüm.** `rows: { type: 'code', ref: '{{ q_tasks.data }}' }`, `groupBy: 'dueDate'` (noktalı yol
183
+ olabilir: `plant.room`). Tarih/saat değeri cihazın yerel gününe (`2026-09-26`) indirgenir; alanı
184
+ boş satırlar en sonda `otherSectionTitle` (varsayılan `Other`) bölümündedir.
185
+ 3. **Çocuklar konumsaldır:** çocuk 0 bölüm başlığı, çocuk 1 öğe şablonu; fazlası çizilmez.
186
+
187
+ ```json
188
+ { "elementId": "tasks", "name": "tasks", "type": "grouped-list",
189
+ "props": { "rows": { "type": "code", "ref": "{{ q_tasks.data }}" },
190
+ "groupBy": { "type": "static", "data": "dueDate" },
191
+ "itemPressable": { "type": "static", "data": true } },
192
+ "children": [
193
+ { "elementId": "day_title", "name": "day_title", "type": "text",
194
+ "props": { "content": { "type": "code", "ref": "{{ Section.title }} · {{ Section.count }}" } } },
195
+ { "elementId": "task_row", "name": "task_row", "type": "container", "props": {},
196
+ "children": [ { "elementId": "task_title", "name": "task_title", "type": "text",
197
+ "props": { "content": { "type": "code", "ref": "{{ item.title }}" } } } ] } ] }
198
+ ```
199
+
200
+ Başlık `Section` okur (`{ key, title, count, items }`), satır kapsamı taşımaz; bölüm dışında
201
+ `Section` boş nesnedir (`Row` gibi). Öğe repeater satırıyla aynıdır: `Row`, `RowIndex`,
202
+ `{{ item.<alan> }}`; akışta tıklanan satır `tasks.selectedItem`. Şablon çocukları setter hedefi
203
+ değildir — satır dışına (sayfadaki bir metne, modala) yazın.
204
+ 4. **Olaylar.** `itemPress { index, row }` (yalnız `itemPressable`; öğe şablonu `list-item`/`button`
205
+ ise dokunuş onun `click`'ine gider), `swipe { key, index, row }` (`swipeActions`),
206
+ `change { selectedIndex }`, `sectionPress` (yük `Section`). `index` `rows` içindeki DÜZ indekstir.
207
+ 5. **Yapışkan gün başlığı.** `stickySectionHeaders: true` + `appearance.heightType: 'fixed'` +
208
+ `heightString`. Sayfanın kendisi kaydırıcıdır; `auto` ya da `fill` liste içerik kadar uzar ve başlık
209
+ yapışmaz. Başlık kabına kendi zeminini verin.
210
+
211
+ ## Ekrana sabit içerik — FAB, alt eylem çubuğu, üst şerit
212
+
213
+ Mobil şemada `style.position`/`zIndex` yoktur (`MOB-002`); sayfa kayarken yerinde duran içerik iki
214
+ bileşenle kurulur. İkisi de sayfanın neresine konursa konsun runtime'da ekran katmanına taşınır —
215
+ düğümü sayfa kök kabının SONUNA koyun (akıştaki yeri boş kalır).
216
+
217
+ - **Sağ alt tek düğme:** `fab` (`position: bottom-right | bottom-center | bottom-left`, `icon`,
218
+ `click`). Sekme çubuğu varsa onun üstünde, kenardan 16 dp.
219
+ - **Alt eylem çubuğu / birden çok düğme / üst ya da orta çapa:** `floating-container`.
220
+ `anchor` 9 nokta (`top-left` … `bottom-right`, varsayılan `bottom-right`), `offsetX`/`offsetY` (dp,
221
+ varsayılan 16, çapalı kenardan içeri), `fullWidth` (alt/üst orta satırı kaplar; `offsetX` yok
222
+ sayılır), `respectSafeArea` (varsayılan açık). Alt eylem çubuğu:
223
+
224
+ ```json
225
+ { "elementId": "action_bar", "name": "action_bar", "type": "floating-container",
226
+ "props": { "anchor": { "type": "static", "data": "bottom-center" },
227
+ "fullWidth": { "type": "static", "data": true } },
228
+ "children": [ { "elementId": "bar", "name": "bar", "type": "container",
229
+ "props": { "direction": { "type": "static", "data": "row" } }, "children": [] } ] }
230
+ ```
231
+
232
+ Kabın kendi yüzeyi yoktur: zemin, köşe, gölge ve iç boşluk içerideki kaba (`surfaceStyle`) verilir.
233
+ Dip sekme çubuğunun tepesidir — `aboveTabBar` diye bir prop YOKTUR. App-bar sayfayla kayar; üst
234
+ çapalı kap app-bar'ın üstüne biner, sabit app-bar'ın altına `offsetY` ile inilir (ör. 72).
235
+ - **Kaydırırken üstte kalan başlık:** `scroll-view` içinde ilk çocuk `stickyHeader: true`, diğer
236
+ doğrudan çocuklar düğüm durumu `states: { isSticky: true }` ile (yalnız dikey; kaydırıcıya `fixed`
237
+ boy verin). Sayfayı örten katman için `modal` / `drawer` widget'ı.
238
+
239
+ `page_screenshot` (viewport karesi, `layoutReport: true`) sabit içeriğin çapalı noktada, sekme çubuğunun
240
+ üstünde durduğunu gösterir; uzun sayfada içerik kabın altından akar.
113
241
 
114
242
  ## Yerleşim — yalnız flex
115
243
 
@@ -127,6 +255,9 @@ Mobil çizer 12 sütunlu ızgarayı **okumaz**: sıra dizi indeksidir, yerleşim
127
255
  alır (`container`, `form`, `safe-area`, `scroll-view`) ve akışları oranlı boyu doldurur; öteki
128
256
  bırakılabilir tipler, widget örneği, sayfa kökü ve `modal`/`drawer` almaz; `image` oranı kendi
129
257
  çizimine yazar.
258
+ - `image` boyu: `heightType: 'fixed'` + `heightString` (`px`/`%`) ya da `'fill'` → görsel kabuğu doldurur
259
+ (`%` ve `fill`de 96 dp taban boyun altına inmez: atası içerik boyundaki kapta yüzde çözülmez); yoksa `aspectRatio` + `heightType: 'auto'` + belli genişlik → boy orandan; ikisi de yoksa 96 dp taban boy
260
+ ve MOB-012 bilgi satırı. Küçük resim için `widthType: 'fixed'` + `widthString` + oran `1` kullanın.
130
261
  - Hiza (`verticalAlign` `top | middle | bottom`, `horizontalAlign` `left | center | right`) web ile
131
262
  aynıdır. Dikey hizanın boşluğu kabın boyundan gelir: `heightType` `fill`, `fixed` ya da oranlı kapta
132
263
  akış kabın boyunu doldurur; boyu içerikten gelen sütun kapta dikey hiza etkisizdir (satır kapta farklı
@@ -139,7 +270,11 @@ Mobil çizer 12 sütunlu ızgarayı **okumaz**: sıra dizi indeksidir, yerleşim
139
270
  React Native'de CSS yoktur; tipli stil grupları **derlenir** (web'in `composeTypedStyle` ikizi,
140
271
  aynı katman sırası: tema taban → gruplar → serbest `style`).
141
272
 
142
- - `surfaceStyle` → zemin, köşe, kenarlık, gölge (tek katman), opaklık, taşma.
273
+ - `surfaceStyle` → zemin, köşe, kenarlık, gölge (tek katman), opaklık, taşma; döndürme `rotate`
274
+ (derece), ölçek `scale` (yüzde, 100 aynı boy), kaydırma `translateX`/`translateY` (px). Sıra web
275
+ ile aynı (kaydır → döndür → ölçekle, köken merkez), düğüm çocuklarıyla döner. Değer birimsiz sayıdır
276
+ (`'15deg'` yazılmaz); aralık dışı (`rotate` ±360, `scale` 10–500, kaydırma ±1000) yazılmaz ve
277
+ `nativeStyle.warnings`'e düşer. Katman ve kabukta (`modal`, `drawer`, `app-bar`) bu dört alan yoktur.
143
278
  - `typographyStyle` → renk, aile (tek ad), punto, kalınlık, hizalama, satır yüksekliği.
144
279
  - Tema token'ları (`var(--ng-*)`) sayılara çözülür; proje teması değişince mobil de değişir.
145
280
 
@@ -148,10 +283,11 @@ aynı katman sırası: tema taban → gruplar → serbest `style`).
148
283
  | Alan | Yerine |
149
284
  |---|---|
150
285
  | `className` | `surfaceStyle` / `typographyStyle` |
151
- | serbest `style` nesnesi (alt seçici `&:hover`, `&__body`) | tipli gruplar; düz anahtarlar (ör. `padding`) çevrilir, alt seçici düşer |
286
+ | serbest `style` nesnesi (alt seçici `&:hover`, `&__body`) | tipli gruplar; düz anahtarlar (ör. `padding`) çevrilir, alt seçici düşer. İstisna `html`: metin (ya da koda bağlı) `style` kendi belgesinin CSS'idir ve çizilir; statik nesne `style` orada da düşer |
152
287
  | `hoverStyle` | yok — dokunmatikte üzerine gelme durumu yoktur |
153
288
  | `tooltip`, `tooltipPosition` | düğümü `tooltip` bileşenine sarın (dokunuşla açılır, yanı `side`) ya da açıklamayı metin olarak koyun |
154
- | `zIndex` | katman için `modal` ya da `drawer` widget'ı |
289
+ | `zIndex`, `style.position` | görsel üstüne bindirme (rozet, hap, tarih etiketi, açıklama bandı, arka plan görseli): `stack` — ilk çocuk taban, ötekiler üst katman; her köşe için `fill`×`fill` bir `container` katmanı kendi hizasıyla; görselin altına binen kart: kartın üst boşluğu negatif (`marginValues.top: -28`); ekrana sabit: `floating-container` / `fab`; yapışkan başlık: `scroll-view` çocuğunda `states.isSticky`; örten katman: `modal` / `drawer` (§Ekrana sabit içerik) |
290
+ | `surfaceStyle`'ın tanımadığı alt anahtar (ör. CSS `transform` metni, `padding`) | grubun tipli alanları; döndürme/ölçek/kaydırma `rotate`, `scale`, `translateX`/`translateY` |
155
291
 
156
292
  Düğümün gerçek stilini görmek: `page_runtime_get({ applicationSlug, pageSlug, resolveStyles: true })`
157
293
  → her düğümde `nativeStyle: { shell, text, warnings }` (`shell` kutunun, `text` içine akan metnin
@@ -217,8 +353,8 @@ Mobil uygulama web ile **aynı sözlüğü ve aynı motoru** kullanır (plans/mo
217
353
  → `translation_update`. Kurulum ve fallback zinciri `platform_guide('translation')`.
218
354
 
219
355
  - **Dil değiştirici:** `language-selector` mobil katalogdadır (`component_get` → `mobileKnowledge`):
220
- `dropdown`un tam ekran `Modal` listesi, tetikleyici `role="combobox"`, seçenekler dilin yerel adıyla
221
- (`tr` → Türkçe). Seçim `machine.setLanguage` ile bütün prop'ları yerinde çözer ve cihazda uygulama
356
+ `dropdown`un listesi (az dilde alanın altında, çok dilde alt sayfada), tetikleyici `role="combobox"`,
357
+ seçenekler `displayMode`'a göre dilin yerel adıyla (`tr` → Türkçe), koduyla (`TR`) ya da ikisiyle. Seçim `machine.setLanguage` ile bütün prop'ları yerinde çözer ve cihazda uygulama
222
358
  çapında yayılır: öteki sekmelerin ekranları ve sekme etiketleri de değişir, makine yeniden kurulmaz.
223
359
  - **Cihazda boot sırası:** uygulama içi seçim → cihazda kayıtlı seçim (`zeyrek:<app>:lang`) → cihaz dili
224
360
  → projenin varsayılanı. `?lang` CİHAZDA YOKTUR; `/preview`'da vardır, bu yüzden `page_screenshot(language:)`
@@ -243,7 +379,7 @@ Motorun kendi adımları (`setter`, `condition`, `microservice`, `code`, `functi
243
379
  |---|---|
244
380
  | `redirect` | yığına iter (göreli yol) ya da dışarıda açar (tam adres) |
245
381
  | `navigate` | eski mobil şemaların adı — `redirect`'in takma adıdır; gateway `redirect`'e çevirir |
246
- | `alert` | tipli uyarı (`success`/`error`/`info`/`warning`) toast'tur ve beklemez; düz uyarı onay bekler |
382
+ | `alert` | tipli uyarı (`success`/`error`/`info`/`warning`) toast'tur ve beklemez; düz uyarı onay bekler. Eylem düğmeli bildirim ("Silindi · Geri al") `alert`le kurulmaz: sayfaya bir `toast` bileşeni koyun (`actions: [{ text, flow }]`, gerekirse `keepOpen`), akış setter'la `isOpen: true` yazar; basış eylemin kendi akışını koşar (`actionClick { text, index }`) |
247
383
  | `confirm` | `yes` / `no` dalları |
248
384
  | `download` | adres cihazın açma sayfasında açılır, gövde paylaşım sayfasına gider (`MOB-005`, bilgi) |
249
385
  | `storage` | cihaz deposu |
@@ -226,7 +226,7 @@ döndürür; hata yok, sayfalama yok. (Bağımsız `pagination` komponenti bu tu
226
226
  | `dropdown.search` · `multi-select.search` | `{ searchText }` | `self.searchText` |
227
227
  | `tabs.tab` | `{ selected }` | `self.selected` ya da `event.data.selected` |
228
228
  | `chart.dataPointClick` | `{ name, seriesName, value, dataIndex, row }` | `self.row` (kaynak satırın tamamı) |
229
- | `repeater.change` · `repeater.select` | `{ selectedIndex }` | `self.selectedIndex` · `self.selectedItem` (akış satır bağlamında koşmaz, `item`/`Row` boş) |
229
+ | `repeater.change` · `repeater.select` | `{ selectedIndex }` | `self.selectedIndex` · `self.selectedItem` (akış satır bağlamında koşmaz, `item`/`Row` boş); satırdaki bir düğmenin akışında setter'ın yazım kapsamı OLAYIN kaynağı değil HEDEFİN ağaçtaki yeriyle belirlenir — satırın şablonu içindeki bir hedefe satır bazlı (`_repeaterValues`), dışındaki bir hedefe (modal, sayfa düzeyi metin) sayfa kapsamında yazılır (ZM-08, 2026-09-26); iç içe repeater zaten `PAGE-013` ile reddedilir, ama bir DIŞ repeater'ın şablonuna İÇ satırdan yazan bir akış varsa yazım dış satırın değil İÇ satırın kimliğiyle anahtarlanır — bilinen açık, PAGE-013 riski düşük tutar |
230
230
  | `html` aksiyonu (`fireEvent`) | ikinci argüman | **`event.data`** |
231
231
  | `kanban.cardMove` | `{ id, row, from, to }` | **`event.data`** (self'e yazılmaz) |
232
232
  | `kanban.cardAdd` · `kanban.cardRemove` | `{ column }` · `{ id, row, column }` | **`event.data`** (self'e yazılmaz; kart eklenmez/gizlenmez, akış yazıp `rows`'u tazeler) |
@@ -249,7 +249,8 @@ dalı bulamaz ve akışı **sessizce durdurur** (`flow-engine.ts:79`); Studio da
249
249
  `sourceBranch === 'always'` ile çizdiği için panelde **Start kartından sonrası boş görünür**.
250
250
  İstisnalar: `condition` → dal adı rule id'sidir (ve o id bir **GUID**'dir — aşağıdaki tuzak 6),
251
251
  `confirm` → `yes|no|always`, `microservice` →
252
- `always` (eski akışlarda `success` de çalışır).
252
+ `always` (eski akışlarda `success` de çalışır) **+ `error`** — bağlarsan hata anında `always`
253
+ yerine ondan sürer (2026-09-26, ADR 0004; ayrıntı aşağıdaki §"4xx de hatadır").
253
254
 
254
255
  **2. `iteratees[].type` yalnız `change` | `fill`.** `setter.ts` başka değerde hiçbir dala
255
256
  girmez — adım çalışır, hiçbir şey yapmaz. `change` tek prop'a değer yazar, `fill` diziyi
@@ -375,16 +376,22 @@ Satır eşlemeli tablo yüklemesinin tam şekli (yükleniyor bayrağı → uç
375
376
  göstermek error flow'un işidir.
376
377
 
377
378
  > **4xx de hatadır ve `always` dalı seni KURTARMAZ.** `microservice.ts` `.catch` bloğu
378
- > reddedilen her istekte koşar — 401/403/404/422 dâhil. Sonrası iki yoldan biridir ve ikisi de
379
- > "ekranda hiçbir şey olmadı" ile biter:
379
+ > reddedilen her istekte koşar — 401/403/404/422 dâhil — ve storage'a HER ZAMAN
380
+ > `{ data: null, headers: null, error: { status, message, body } }` yazar (ADR 0004,
381
+ > 2026-09-26 — eskiden hata gövdesi `data`'ya yazılıyordu). `.finally` sonra ÜÇ yoldan birini
382
+ > dener, SIRAYLA:
380
383
  >
381
- > 1. **Hata yolu VARSA** `engine.onError` true döner ve `.finally` `return` eder —
382
- > `engine.next('always')` **hiç çağrılmaz**. Yani `always` ile `errorSteps` birbirini dışlar;
383
- > ikisini birden kuramazsın, hata yolu ana akışın devamını devralır.
384
- > 2. **Hata yolu YOKSA** `always` koşar, ama storage artık `{ error, data }`'dır ve
385
- > `Steps.<ad>.data` başarı gövdesi değil **hata gövdesidir**. Sıradaki `condition`
386
- > `!!Steps.signIn.data.token` gibi bir ifadeyi `undefined` üzerinden değerlendirir, sessizce
387
- > diğer dala düşer ve `else` dalı bağlı değilse akış orada biter.
384
+ > 1. **Adımın kendi `error` dalı BAĞLIYSA** akış oradan sürer — `always` **hiç koşmaz**
385
+ > (`hasBranch('error')`). Bu dal `Steps.<ad>.error` okuyabilir; ana akışın kalanı hiç çalışmaz.
386
+ > 2. **`error` dalı yoksa, `errorSteps` VARSA** `engine.onError` true döner ve `.finally` `return`
387
+ > eder — yine `always` koşmaz, flow'un hata akışı devralır (aşağıdaki karşılaştırma tablosu).
388
+ > 3. **İkisi de yoksa** `always` koşar, ama `Steps.<ad>.data` artık **NULL**dır — başarı gövdesi
389
+ > değil, hiç gövde değil. Sıradaki bir `condition` `!!Steps.signIn.data.token` gibi bir
390
+ > ifadeyi `null.token` üzerinden dener; `runCode` hatayı yutar (K2), ifade `undefined` döner ve
391
+ > akış sessizce diğer dala düşer — `else` dalı bağlı değilse akış orada biter. `data`yı bir
392
+ > liste prop'una yazan bir setter artık patlayan bir nesne değil `null` görür (repeater/table
393
+ > `null`'ı boş liste sayar), ama hata OLDUĞUNU fark etmeden geçer — gateway bunu **FLOW-028**
394
+ > ile uyarır.
388
395
  >
389
396
  > **`errorSteps` yalnız `start` taşıyorsa hata yolu YOKTUR:** `hasErrorFlow`
390
397
  > `errorTasks.some(t => t.type !== 'start')` arar. Start'ın ardına en az bir adım bağla —
@@ -393,15 +400,18 @@ göstermek error flow'un işidir.
393
400
  > **Mobil uygulama:** mobil runtime (`@ng-apps/ui-native` `NativeRuntime`) `errorSteps`'i web gibi
394
401
  > `errorTasks`'a eşler — hata yolu iki platformda aynı koşar.
395
402
 
396
- > **Hata metni ÜÇ ayrı yerde durur ve ikisi kullanıcıya gösterilecek metin DEĞİLDİR.**
403
+ > **Hata metni birden çok yerde durur, hepsi aynı şey değildir (2026-09-26 itibarıyla, ADR 0004).**
397
404
  > `onError` hata akışını koşturmadan önce `machine.setStorage('error', errorInfo)` yapar
398
- > (`flow-engine.ts`), yani hata akışının adımları `Steps.error`'ı okuyabilir:
405
+ > (`flow-engine.ts`), yani hata akışının adımları `Steps.error`'ı okuyabilir; patlayan adımın
406
+ > KENDİ storage'ı ise (bağlı `error` dalı da `always` da) `Steps.<adımAdı>.error` nesnesini taşır:
399
407
  >
400
408
  > | İfade | Ne taşır | Kaynak |
401
409
  > |---|---|---|
402
- > | **`Steps.error.message`** | **Ucun kendi mesajı** — `err.response.data.message`; boşsa HTTP durum metnine düşer. **Kullanıcıya gösterilecek metin budur.** Yanında `Steps.error.code` · `Steps.error.step.name` (patlayan adım) · `Steps.error.step.data` (ucun gövdesi) gelir | `microservice.ts` `err.reason \|\| HttpStatusText[err.code]` → `flow-engine.ts` `onError` |
403
- > | `Steps.<adımAdı>.error` | Yalnız HTTP **durum metni** — 401 için `'Unauthorized'`. Hata OLDUĞUNU anlamaya yarar, göstermeye değil | `microservice.ts` `.catch` `HttpStatusText[err.code]` |
404
- > | `Steps.<adımAdı>.data` | Ucun **hata gövdesi** (`err.response.data`) — başarı gövdesi değil. `.message` alanı burada da vardır | `microservice.ts` `.catch` |
410
+ > | **`Steps.error.message`** | **Yalnız `errorSteps` içinde** — ucun kendi mesajı, `err.reason` → HTTP durum metni → `'Service request failed'`. Yanında `Steps.error.code` · `Steps.error.step.name` (patlayan adım) · `Steps.error.step.data` (ucun hata gövdesi; parametre ifadesi patladıysa `null`) gelir | `microservice.ts` `err.reason \|\| HttpStatusText[err.code]` → `flow-engine.ts` `onError` |
411
+ > | `Steps.<adımAdı>.error.message` | Patlayan adımın KENDİ storage'ında ucun mesajı — `error` dalında da `always`'te de okunur, sıra aynı: `err.reason` → HTTP durum metni → `'Service request failed'` | `microservice.ts` `.catch` |
412
+ > | `Steps.<adımAdı>.error.status` | HTTP durum KODU (sayı) — parametre ifadesi patladıysa `null` | `microservice.ts` `.catch` |
413
+ > | `Steps.<adımAdı>.error.body` | Ucun hata gövdesi — eski `Steps.<adımAdı>.data`'nın taşındığı yer | `microservice.ts` `.catch` |
414
+ > | `Steps.<adımAdı>.data` | **HER ZAMAN `null`**'dır — başarı gövdesi değil, hiçbir gövde değil (eskiden hata gövdesiydi) | `microservice.ts` `.catch` |
405
415
  >
406
416
  > **`alert` adımının `message`'ı DÜZ METİNDİR — ifade yazarsan ekranda o ifade görünür.**
407
417
  > `alert.ts` `task.argument.message`'ı doğrudan `toast.error()`'a geçirir ve `args` hiçbir yerde
@@ -429,15 +439,24 @@ göstermek error flow'un işidir.
429
439
  > `{ "type": "confirm", "args": { "message": "Bu risk ve 4 aksiyonu silinecek.", "title": "Riski sil",
430
440
  > "confirmText": "Sil", "cancelText": "Vazgeç" } }`.
431
441
 
432
- > **Hangi yolu seçeceksin — `errorSteps` mi, ana akışta `condition` mı?** İkisi birden
433
- > kurulamaz (yukarıdaki 1. madde), yani bu bir tercih değil bir karar:
442
+ > **Hangi yolu seçeceksin — adımın kendi `error` dalı mı, `errorSteps` mi, ana akışta
443
+ > `condition` mı?** Bir hatada sırayla denenir (yukarıdaki 4xx bölümü) — üçü birden TANIMLI
444
+ > olabilir ama aynı anda yalnız biri koşar: **`error` dalı bağlıysa o kazanır**, yoksa
445
+ > `errorSteps`, ikisi de yoksa `always` + `condition`:
434
446
  >
435
- > | | `errorSteps` | ana akışta `condition Steps.<adımAdı>.error` |
436
- > |---|---|---|
437
- > | Ana akışın kalanı | **koşmaz** — hata akışı devralır | koşar, dalı sen seçersin |
438
- > | Yükleniyor bayrağı · modal kapatma · tablo tazeleme | hata akışında **yeniden kurmak zorundasın**; kurmazsan bayrak açık kalır | zaten ana akışta duruyor |
439
- > | Hata metni | `Steps.error.message` (yukarıdaki tablo) | `Steps.<adımAdı>.data.message`, hata olduğunu `Steps.<adımAdı>.error` söyler |
440
- > | Ne zaman doğru | hata anında akış **tamamen başka bir yola** sapacaksa (başarısız giriş, yetkisiz sayfa) | kalan adımların çoğu hata hâlinde de koşacaksa — **form gönderimlerinin varsayılanı budur** (Kural 7) |
447
+ > | | adımın kendi `error` dalı (g-02) | `errorSteps` | ana akışta `condition !!Steps.<adımAdı>.error` |
448
+ > |---|---|---|---|
449
+ > | Kapsam | yalnız BU adımın hatası | akıştaki HERHANGİ bir adımın hatası | yalnız BU adımın hatası |
450
+ > | Ana akışın kalanı | **koşmaz** — bu dal devralır | **koşmaz** — hata akışı devralır | koşar, dalı sen seçersin |
451
+ > | Yükleniyor bayrağı · modal kapatma · tablo tazeleme | dalda **yeniden kurmak zorundasın** | hata akışında **yeniden kurmak zorundasın**; kurmazsan bayrak açık kalır | zaten ana akışta duruyor |
452
+ > | Hata metni | `Steps.<adımAdı>.error.message` | `Steps.error.message` (yukarıdaki tablo) | `Steps.<adımAdı>.error.message`; hata olduğunu `!!Steps.<adımAdı>.error` söyler — `.error.status` parametre ifadesi patladıysa `null`dur, o yolu KAÇIRIR |
453
+ > | Ne zaman doğru | hata anında BU adımın sonrası **tamamen başka bir yola** sapacaksa | hata anında akış **tamamen başka bir yola** sapacaksa (başarısız giriş, yetkisiz sayfa) | kalan adımların çoğu hata hâlinde de koşacaksa — **form gönderimlerinin varsayılanı budur** (Kural 7) |
454
+ >
455
+ > **"Ana akışta condition" sütunu `.error`'u okumalı, `.data`'yı değil.** `Steps.<adımAdı>.data`'nın
456
+ > DOLU/BOŞ olmasına bakarak başarı/hata ayırmak (`data.token` gibi bir alanın varlığını yoklamak)
457
+ > artık yanıltıcıdır — `data` hata anında zaten `null`'dur (yukarıdaki §"4xx de hatadır"). Böyle bir
458
+ > `condition`'ı gateway **FLOW-028** ile uyarır; adıma ayrıca bir `error` dalı bağlayın (aşağıdaki
459
+ > 7c örneği) — `error` dalı bağlıysa uyarı düşer, çünkü hata artık `always`'e hiç uğramaz.
441
460
 
442
461
  Zorunlu filtresi olan uçlarda (`patient-*`, `doctor-appointments` gibi `isRequired: true`
443
462
  filtreli microservice'ler) parametreyi adımda ver, yoksa uç `MissingParameterException` atar:
@@ -1094,15 +1113,22 @@ UYGULAMAZ** — koordinatları aynen kaydeder (aşağı itme yalnız `page_creat
1094
1113
  > SONRAKİ ağaca bakarak. Layout'ta da geçerlidir: kırılma
1095
1114
  > `_repeaterValues` geri yazmasındadır, barındıranın sayfa mı layout mu olduğuna bakmaz.
1096
1115
  >
1116
+ > **Mobil uygulamada** başlıklar altında gruplu liste (güne/kategoriye göre) iç içe repeater
1117
+ > gerektirmez: tek `grouped-list` (`groupBy` + başlık `{{ Section.title }}` + öğe şablonu) —
1118
+ > `platform_guide('mobile')` §Güne göre görev listesi. PAGE-013 mesajı mobilde bunu önerir.
1119
+ >
1097
1120
  > **Yapılacak:** satır içi listeyi tek bir `textarea`'da **satır başına bir öğe** olarak topla
1098
1121
  > (`options_text`), kaydederken `split('\n')`'le böl. Ölçülmüş çalışan sürüm: anket editörü
1099
1122
  > (`anket-admin/anket-duzenle`). Gerçekten satır satır düzenleme gerekiyorsa iç listeyi AYRI bir
1100
1123
  > sayfada/modalda yönet — iç içe repeater bunu karşılamıyor.
1101
1124
 
1102
1125
  > **Repeater satırını akıştan silmek/eklemek:** repeater ÇOCUĞUNDAKİ bir butonun click akışında
1103
- > `RowIndex` doğru satırı verir, ve hedef repeater'ın KENDİSİ olduğu için `rows`'a yazmak serbesttir
1104
- > (`isRepeaterSelf` dalı satır-değeri yoluna girmez). Ölçülmüş desen:
1105
- > `q_list.rows = (q_list.rows || []).filter(function(r, i){ return i !== RowIndex; })`.
1126
+ > tıklanan satır olay makineye gitmeden SEÇİLİR, yani `q_list.selectedIndex` o satırdır — her runtime
1127
+ > sürümünde. Hedef repeater'ın KENDİSİ olduğu için `rows`'a yazmak serbesttir (`isRepeaterSelf` dalı
1128
+ > satır-değeri yoluna girmez). Desen:
1129
+ > `q_list.rows = (q_list.rows || []).filter(function(r, i){ return i !== q_list.selectedIndex; })`.
1130
+ > `RowIndex` de aynı satırı verir, ama yalnız ARC-121 düzeltmesini (2026-09-27) taşıyan runtime'da;
1131
+ > eskisinde akışta tanımsızdı.
1106
1132
  > Ekleme aynı şekilde `.slice()` + `push` ile yapılır; satır numarasını ekledikten sonra yeniden ata.
1107
1133
 
1108
1134
  **Gönderimi saklamak:** satır başına normalize kayıt yerine tek `TEXT` kolonda anlık görüntü
@@ -1376,6 +1402,16 @@ alanları silinir. Gateway bu yüzden `page_update`'te sayfayı önce okur, yaln
1376
1402
  alanları değiştirip tam gövdeyi geri yazar (`layout_update` ve `component_update` de aynı).
1377
1403
  Yine de `schema` parametresini elle doldurursan okuduğun hâli değil, **verdiğin liste** yazılır.
1378
1404
 
1405
+ **`page_update` sürümsüz yazmaz (PAGE-039).** Platform sayfa PUT'unu `baseVersion` olmadan reddeder.
1406
+ `baseVersion` vermezsen gateway bu sayfada en son gördüğü sürümü gönderir: `page_get` ya da
1407
+ `page_schema_get` yanıtındaki `version`, `page_schema_patch`'in `newVersion`'ı ya da önceki
1408
+ `page_update`'in yazdığı sürüm. Hiçbiri yoksa istek **gönderilmez** — önce oku. Sayfa o sürümden beri
1409
+ değiştiyse (Studio'da açık sekme, başka ajan) 409 döner; mesaj güncel sürümü ve son yazmayı (zaman,
1410
+ kullanıcı, kaynak, revizyon) söyler ve hiçbir şey yazılmaz. Doğru yol yeniden okuyup değişikliği
1411
+ `page_schema_patch` ile yapmaktır; aradaki değişikliği bilerek ezmek için `force: true` son sürümü
1412
+ okuyup onun üstüne yazar ve yanıt `forced: true` taşır. Sebep: Bitki Bakım koşusunda sürümsüz bir
1413
+ tam gövde yazımı ana sayfayı ve profili boşalttı, akışları sildirdi (14:02).
1414
+
1379
1415
  ### Atomiklik: 500 KB'lık şemada tek komponente dokunmak
1380
1416
 
1381
1417
  `page_schema_patch` şemanın tamamını göndermez — yalnız op'ları gönderir. Tam gövde ne LLM
@@ -1385,7 +1421,8 @@ ve geri yazar. Sözleşme (`PatchPageSchemaCommandHandler`):
1385
1421
  | Aşama | Ne yapar |
1386
1422
  |---|---|
1387
1423
  | Kilit | Sayfa başına 60 sn'lik dağıtık kilit; başka bir aktör yazıyorsa istek `PageSchemaLockedException` ile döner (409) |
1388
- | Sürüm | `baseVersion` verdiysen `UpdatedAt.Ticks` ile karşılaştırılır; tutmazsa `PageSchemaVersionConflictException` (409) ve **hiçbir op uygulanmaz** |
1424
+ | Sürüm | `baseVersion` verdiysen `UpdatedAt.Ticks` ile karşılaştırılır; tutmazsa `PageSchemaVersionConflictException` (409) ve **hiçbir op uygulanmaz** — bütün op'lar etkisizse hariç (aşağıdaki "Tekrar" satırı) |
1425
+ | Tekrar | `add_component`'in `elementId`'si zaten varsa ve düğüm istenen ebeveynde, istenen içerikle duruyorsa op **etkisizdir** (hata değil; konum karşılaştırılmaz). Hedef düğümü değiştirmeyen `replace_prop`/`set_*`/`add_event` da etkisizdir. Bütün op'lar etkisizse yama yazılmaz, sürüm artmaz ve eski `baseVersion`'a rağmen 200 `noop: true` döner; sürüm eskiyse `stale: true` ve `newVersion` sizin gönderdiğinizdir. Aynı id'de farklı düğüm hâlâ 422 `duplicate-id`'dir |
1389
1426
  | Uygulama | Her op hedefini ağaçta arar (`Find` → JSON Pointer) ve **yalnız o düğüme** dokunur; kardeşler, üst düğümler ve diğer alt ağaçlar bit düzeyinde korunur |
1390
1427
  | Hep-ya-hiç | Op'lar önce bellekte uygulanır; **tek bir hata** (bilinmeyen op, bulunamayan hedef, tekrar eden elementId, doğrulama) tüm komutu düşürür — kısmi yazma yoktur |
1391
1428
  | Doğrulama | Tam şema `PageSchemaValidator`'dan geçer (422 + alan bazlı `errors`) |
@@ -1394,7 +1431,18 @@ ve geri yazar. Sözleşme (`PatchPageSchemaCommandHandler`):
1394
1431
 
1395
1432
  Yanıt üç alan döner: `applied` (uygulanan op sayısı), **`appliedPatch`** (her op'un şemada tam
1396
1433
  olarak hangi JSON Pointer'a ne yaptığı — RFC 6902 biçiminde; "gerçekten yalnız o komponente mi
1397
- dokunuldu" sorusunun kanıtı) ve **`newVersion`** (`UpdatedAt.Ticks`).
1434
+ dokunuldu" sorusunun kanıtı) ve **`newVersion`** (`UpdatedAt.Ticks`). Etkisiz op varsa `unchanged`
1435
+ (elementId listesi) ve `alreadyApplied` cümlesi gelir; hepsi etkisizse `noop: true`.
1436
+
1437
+ **Yanıt kaybolursa aynı gövdeyle tekrarla.** Ağ koptu, zaman aşımı oldu ve yamanın uygulanıp
1438
+ uygulanmadığını bilmiyorsan aynı `page_schema_patch`'i **aynı gövde ve aynı `baseVersion`** ile
1439
+ yeniden gönder. Uygulanmışsa `noop: true` + `alreadyApplied` döner, hiçbir şey ikinci kez yazılmaz;
1440
+ uygulanmamışsa yama şimdi uygulanır. `add_component` (gövdede `id` verme — platform her istekte yeni
1441
+ `id` üretir, karşılaştırma onu yok sayar), aynı değeri yazan `replace_prop`/`set_*` ve aynı bağı kuran
1442
+ `add_event` tekrarda etkisizdir. İki istisna: `remove_component` ikinci kez `unknown-target` döner — o
1443
+ durumda `page_schema_get` ile bak; eklediğin düğümün bir prop'unu aynı yamanın sonraki op'u
1444
+ değiştirdiyse tekrar `duplicate-id` alır. Yanıt `stale: true` taşıyorsa sayfa sürümünden beri başka bir
1445
+ yazımla da değişmiştir: devam etmeden önce yeniden oku. `idempotencyKey` yoktur ve gerekmez.
1398
1446
 
1399
1447
  > **`appliedPatch` gövdesi VARSAYILAN OLARAK iskelettir.** Girdi `{ op, path }` döner; eklenen
1400
1448
  > düğüm `component` altında yalnız `id`/`elementId`/`name`/`type`/`children` taşır. Kanıt zaten
@@ -1414,12 +1462,29 @@ yamanın `baseVersion`'ıdır. Kontrol platformda atomiktir (satır kilidi + sü
1414
1462
  `UPDATE`'te), yani iki çağrı arasına giren yazarı da yakalar. `baseVersion` vermezsen gateway yalnız
1415
1463
  bu çağrıda zaten okuduğu sayfanın sürümünü gönderir; hiç okumadıysa yama sürümsüz gider ve son
1416
1464
  yazan kazanır. Eski `expectedUpdatedAt` hâlâ çalışır ama `baseVersion`'ın yerini tutmaz.
1465
+
1466
+ **Studio da sürüm gönderir (2026-09-26, ZM-27).** Önceden Studio'da açık kalan bir sekme, yayını
1467
+ kaçırdığı yamalarının üstüne kendi eski (bir kez boş) şemasını yazıyor ve bağı kalmayan akışları
1468
+ sildiriyordu. Artık Studio'nun her kaydı sürüm taşır: eski sekme 409 alır ve taslağını senin
1469
+ yazdığının üstüne birleştirir. `revision_list`'te yamandan sonra `source: studio` satırı görürsen bu
1470
+ artık bir üzerine yazma değil, birleştirilmiş bir kayıttır — yine de `revision_diff` ile bak.
1417
1471
  Yanıttaki `touched`, kaydı değişen elementId'leri listeler (eklenen alt ağaç dâhil).
1418
1472
 
1419
1473
  > **Büyük şemada çalışma sırası:** `mode:'summary'` ile ağacı çıkar (props yok, 500 KB'lık sayfada
1420
1474
  > bile küçük) → hedefin `elementId`'sini al → gerekiyorsa `mode:'subtree'` ile yalnız o dalı oku →
1421
1475
  > op'ları `page_schema_validate` ile kuru koştur → `page_schema_patch`. `mode:'full'` yalnız
1422
1476
  > gerçekten tüm şema gerektiğinde çağrılır.
1477
+ >
1478
+ > **`full` sığmazsa hata verir, kırpmaz.** Yanıt boyut sınırını aşan şemada `mode:'full'` hata döner
1479
+ > ve düğüm sayısını söyler. Tamamı gerekiyorsa `cursor:'0'` ile oku ve `nextCursor` gelmeyene kadar
1480
+ > sür: her parça `items[]` taşır, kalem `parentElementId` düğümünün `children[index]`'inin yerine
1481
+ > geçer (`parentElementId` yoksa kök dizinin `[index]`'i); alt ağacı sığmayan düğüm kabuk gelir
1482
+ > (`children: [{ elementId, deferred: true }]`) ve sonraki kalemler onu doldurur. Toplanan düğüm sayısı
1483
+ > `nodeCount`'a eşit değilse okuma eksiktir. Okuma arasında sayfa değişirse 409 — baştan başla.
1484
+ > `allowTruncated:true` kırpılmış gövdeyi açıkça verir (`truncated: true`); o gövdeye dayanan
1485
+ > `page_schema_patch` PAGE-038 uyarısı taşır. **`page_update`'e kırpık ya da yarım şema gönderme:**
1486
+ > PUT tam REPLACE'tir, eksik düğümler geri dönüşsüz silinir — gateway düğüm eksilten şemayı
1487
+ > `allowNodeLoss:true` olmadan PAGE-037 ile reddeder.
1423
1488
 
1424
1489
  > **`remove_component`'in yan etkisi — tek istisna.** Sayfa kaydedilirken artık hiçbir komponent
1425
1490
  > olayının, prop'unun ya da sayfa olayının referans vermediği akışlar platformca **kalıcı olarak
@@ -1844,15 +1909,24 @@ page_container
1844
1909
  ```
1845
1910
  start → [setter] btn.isLoading = true, btn.isDisabled = true, notification gizle
1846
1911
  → [microservice] sign-in (POST; email + password body'den)
1847
- → [condition] !!Steps.signIn.data.token
1848
- ├─ always → [setter] buton durumunu sıfırla
1849
- ├─ if → [setcookie] { name:'APP_Token', value:'Steps.signIn.data.token',
1850
- │ expiresInHours: 240 }
1851
- │ → [redirect] hedef sayfaya
1852
- └─ else → [setter] notification.content =
1853
- { type:'code', ref:'Steps.signIn.data.message || "Giriş başarısız"' }
1912
+ ├─ error → [setter] btn.isLoading=false, btn.isDisabled=false,
1913
+ │ notification.content =
1914
+ │ { type:'code', ref:'Steps.signIn.error?.message ?? "Giriş başarısız"' }
1915
+ └─ always → [condition] !!Steps.signIn.data.token
1916
+ ├─ always → [setter] buton durumunu sıfırla
1917
+ ├─ if → [setcookie] { name:'APP_Token', value:'Steps.signIn.data.token',
1918
+ │ expiresInHours: 240 }
1919
+ │ → [redirect] hedef sayfaya
1920
+ └─ else → [setter] notification.content =
1921
+ { type:'code', ref:'Steps.signIn.error?.message ?? "Giriş başarısız"' }
1854
1922
  ```
1855
1923
 
1924
+ - **`sign-in` adımının `error` dalı burada ZORUNLUDUR, süs değil.** Dal bağlı değilken bu şablonun
1925
+ `condition`'ı `Steps.signIn.data.token`'ı okur ve gateway'i **FLOW-028** ile uyarır (yukarıdaki
1926
+ "Hangi yolu seçeceksin" notu): hata anında `data` `null`'dur, token okuması sessizce `undefined`
1927
+ döner. Dal bağlanınca hata onun üzerinden sürer — `always`/`condition` yalnız BAŞARIDA çalışır,
1928
+ yani `data.token` orada gerçek bir iş kuralıdır (uç 200 dönüp token'sız yanıt verirse), hata
1929
+ algılama YÖNTEMİ değil.
1856
1930
  - **Cookie adı middleware'in okuduğu adla birebir aynı olmalı** (`cookie.APP_Token` —
1857
1931
  `platform_guide('middleware')` Tarif 1). Uyuşmazlık sessizce "hep 401" demektir.
1858
1932
  - `redirect` adımı `isRaw: true` iken `path` **çok satırlı JS bloğudur** (son ifade değeri döner) —
@@ -2042,9 +2116,14 @@ hiçbiri** bağlı değildi; üç portalın da rol menüsü boş çıkıyor, pan
2042
2116
  > (`dto_get` ile bak: genelde `role`, `role_name`, `roles_csv` vardır). Sayfa tarafında CSV
2043
2117
  > kullan — ayrıntı ve şablon `platform_guide('expressions')` §2.
2044
2118
  >
2045
- > **Uç hata dönerse `.data` dizi değildir.** 401/404 sonrası `Steps.x.data` bir nesnedir ve
2046
- > `rows || []` bunu yakalamaz (nesne truthy) → `rows.filter is not a function` /
2047
- > `(items ?? []).map is not a function`. `Array.isArray(Steps.x?.data) ? Steps.x.data : []` yaz.
2119
+ > **Uç hata dönerse `.data` NULL'dur, hata gövdesi değil (ADR 0004, 2026-09-26).** 2026-09-26'ya
2120
+ > kadar `Steps.x.data` 401/404 sonrası hata gövdesiydi ve `rows || []` bunu yakalamıyordu (nesne
2121
+ > truthy) → `rows.filter is not a function` / `(items ?? []).map is not a function`; artık uç hata
2122
+ > dönünce `data` **null**'dur ve bu adım boş veriyle koşar — `rows` gibi bir listeye bağlarsan
2123
+ > bileşen `null`'ı boş liste sayar, `Array.isArray(...) ? ... : []` sarmalamaya artık gerek yok.
2124
+ > Hata OLDUĞUNU fark etmeden geçmemek için `error` dalı bağlayın (yukarıdaki §"4xx de hatadır",
2125
+ > gateway bunu bağlı değilken **FLOW-028** ile uyarır); hata gövdesini görmek istiyorsan
2126
+ > `Steps.x.error.body`'yi okuyun.
2048
2127
 
2049
2128
  ## ⚠️ `page_schema_patch` `html` komponentini reddediyor — katalogda mükerrer kayıt (2026-08-19)
2050
2129
 
@@ -2107,6 +2186,31 @@ micro-op hattına dönün.
2107
2186
  > uyarı yoktu. Silinmesini bilerek istiyorsanız çağrıyı `allowFlowDeletion: true` ile tekrarlayın;
2108
2187
  > yanıt `deletedFlows` ile hangi akışların gittiğini listeler.
2109
2188
  >
2189
+ > **Silinen akış geri kurulabilir (2026-09-26, ZM-27).** Sayfa kaydının sildiği akış artık revizyon
2190
+ > defterine `operation: delete` satırı olarak düşer (kaynağı `studio` ya da `mcp`). `flow_restore`
2191
+ > akışı son revizyonundaki ad, slug ve adımlarla AYNI id ile yeniden kurar; olaya bağlamaz —
2192
+ > geri geldikten sonra `set_event`. İki sınır: revizyon saklama süresi (budanan akış 404) ve
2193
+ > adımların hedeflediği komponentler — akış bir komponenti tetikleyen/parametre olarak
2194
+ > gösteriyorsa o komponent şemada durmalıdır (canlı ölçüldü: `flow_restore` "Trigger Element Id"
2195
+ > dış anahtarına çarpar).
2196
+ >
2197
+ > **Boşalan sayfa: `page_restore` (2026-09-26).** Sayfayı ve akışlarını birlikte geri kurar:
2198
+ > `revision_list(module: 'page', recordId: <pageId>)` → boşalmadan önceki revizyonu bul (`source` ve
2199
+ > `correlationId` yardım eder; `revision_get` ile içeriğine bak) → `page_restore({ revisionNo })`.
2200
+ > Platform önce eksik akışları adımsız kurar, sonra şemayı eski komponent id'leriyle yazar, sonra
2201
+ > adımları yazar — akış→komponent ve olay→akış dış anahtarları bu sırayla tek transaction'da çözülür.
2202
+ > Yanıt `restoredFlows` ve `deletedFlows` (N'den sonra bağlanmış, şimdi yetim kalan) döner.
2203
+ > Revizyonu kalmamış bir akış varsa 409 ve hiçbir şey yazılmaz.
2204
+ >
2205
+ > **Doğru revizyonu seç.** Şemayı yüklemeyen komutların (`UpdateProjectCommand`,
2206
+ > `UpdateApplicationCommand`…) yazdığı sayfa revizyonları komponent taşımaz; komponenti olan sayfayı
2207
+ > böyle bir revizyona döndürmek 409 `PageRevisionEmptyException` ile reddedilir (bilerek boşaltmak için
2208
+ > `allowEmpty: true`). `correlationId`'si `PatchPageSchemaCommand` ya da `UpdatePageCommand` olan
2209
+ > revizyonu seç. İki sınır daha: N'den beri silinen layout/rol/middleware/özel komponent ya da başka
2210
+ > sayfanın aldığı slug geri yüklemeyi dış anahtar/benzersizlik hatasıyla düşürür (transaction geri
2211
+ > alınır, veri kaybı yok); yalnız başka bir akışın adımından çağrılan (olaya bağlı olmayan) akışlar
2212
+ > geri kurulmaz — onlar için `flow_restore`.
2213
+ >
2110
2214
  > **FLOW-018 artık silinen düğümün ALT AĞACINI da görüyor.** Eskiden yalnız op'un kendi
2111
2215
  > `target`'ına bakılıyordu: bir kapsayıcı silinince ÇOCUĞUNA yazan setter sessizce kopuyor ve
2112
2216
  > uyarı hiç çıkmıyordu (09-05, `sign_in_form` → `btn_sign_in`).
@@ -2150,6 +2254,9 @@ micro-op hattına dönün.
2150
2254
  style + Kural 1h deseniyle yeniden kurulunca kart 400px'e ve ortaya oturdu. Gateway bunu **PAGE-014 (uyarı)** ile
2151
2255
  yanıtın `layoutWarnings` alanında bildirir. **Düzeni `style` ile kurmaya hiç kalkışma:**
2152
2256
  ortalama/yaslama/aralık işi kapsayıcının kendi `layout: 'flex'` prop grubuna aittir (Kural 1f-2).
2257
+ **İstisna `html`:** onun `style`'ı yazarın işaretlemesine kapsamlı giren CSS metnidir (web'de iç
2258
+ seçicili sınıf, mobilde belgenin stil bloğu) — string asıl biçimdir ve PAGE-014 onda susar. Mobilde
2259
+ `html`'e nesne `style` verme: çizilmez (MOB-002).
2153
2260
  - **Menüde "bulunduğun sayfa" vurgusu: `sidebar`/`layout`'ta `activeHref`, `header`'da
2154
2261
  `activeNavHref`.** `sidebar` ve `layout` menü ağacını ortak renderer'la çizer: `activeHref` boşken
2155
2262
  tarayıcıdaki adrese uyan satırı kendileri vurgular, prop yazılırsa o satır vurgulanır (akıştan
@@ -98,7 +98,10 @@ theme_update({ theme: {
98
98
  ```
99
99
 
100
100
  **Yazı tipi ve ölçek:** `fontFamily` panelin Google Fonts listesinden bir ad olmalı — runtime
101
- fontu ADIYLA Google'dan çeker, tanınmayan ad sessizce sans-serif'e düşer (THEME-008).
101
+ fontu ADIYLA Google'dan çeker, tanınmayan ad sessizce sans-serif'e düşer (THEME-008). Mobilde de
102
+ aynı kural geçerlidir: cihaz aynı adı Google Fonts'tan TTF olarak indirip `expo-font` ile yükler
103
+ (400 ve 700 kalınlık) — Android'de kalın yüz SENTETİKTİR (iOS gerçek kalın dosyayı çizer,
104
+ ZM-10, 2026-09-26).
102
105
 
103
106
  ```jsonc
104
107
  theme_update({ theme: {
@@ -648,9 +648,15 @@ Tam örnek, ölçülen çıktı şekli ve `code` çocuğu tuzağının çıkış
648
648
 
649
649
  ```js
650
650
  // dizi (isSingle:false) · nesne (isSingle:true) · düz değer (yalnız pk dönen yazma)
651
- (Array.isArray(x.data) ? (x.data[0] && x.data[0].id) : (x.data && x.data.id !== undefined ? x.data.id : x.data))
651
+ (x.data && typeof x.data !== 'string' && typeof x.data.length === 'number' ? (x.data[0] && x.data[0].id) : (x.data && x.data.id !== undefined ? x.data.id : x.data))
652
652
  ```
653
653
 
654
+ Diziyi `Array.isArray` ile değil `.length` ile tanıyın: 2026-09-27'den önceki backend'de tablo
655
+ adımının listesi Jint'e sarılı bir CLR nesnesi olarak iniyordu ve `Array.isArray(x.data)` `false`
656
+ dönüyordu — koşuda (2026-09-24) giriş akışı bu yüzden her kullanıcıyı 401 ile reddetti
657
+ (`zeyrek-backend/docs/incident-log.md` #89). `.length`, `.map`, `.filter` o sürümde de çalışıyordu;
658
+ düzeltilmiş backend'de `Array.isArray` de doğrudur.
659
+
654
660
  Şekli bilmiyorsan bu yazımı kullan; `workflow_preview` çıktısında `steps[].output`'a bakarak
655
661
  hangi vakada olduğunu da görebilirsin.
656
662
 
@@ -1,6 +1,6 @@
1
1
  {
2
- "hash": "f37265d598ea468deb2157165891629c2e88c7fd2238e82ec60cda7f58f7de4c",
3
- "generatedAt": "2026-09-18T12:57:15.416Z",
2
+ "hash": "466761837eb9a463fe5984ca119fc04fbdc946cf363177b71efd1b195b8729ca",
3
+ "generatedAt": "2026-09-27T17:47:09.197Z",
4
4
  "types": 186,
5
5
  "files": 108,
6
6
  "note": "Şemaları üreten backend kaynaklarının parmak izi. Elle düzenlemeyin: 'npm run generate-schemas' yazar, 'npm run schema:check' denetler.",