@appaflytech/zeyrek-ai-mcp 0.0.40 → 0.0.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/knowledge/componentCatalog.js +95 -104
  2. package/dist/knowledge/componentEvents.js +34 -18
  3. package/dist/knowledge/mobileComponentCatalog.js +119 -20
  4. package/dist/knowledge/mobileComponentEvents.js +41 -0
  5. package/dist/knowledge/motionResolve.js +252 -0
  6. package/dist/knowledge/normalize.js +117 -71
  7. package/dist/knowledge/pageDiscover.js +2 -2
  8. package/dist/knowledge/platform/batchOps.js +70 -0
  9. package/dist/knowledge/platform/heroTag.js +153 -0
  10. package/dist/knowledge/platform/lint.js +128 -6
  11. package/dist/knowledge/platform/mobile.js +1 -1
  12. package/dist/knowledge/platform/mobileLayoutLint.js +8 -1
  13. package/dist/knowledge/platform/mobileRules.js +18 -0
  14. package/dist/knowledge/platform/pageTransition.js +57 -0
  15. package/dist/knowledge/platform/web.js +1 -1
  16. package/dist/knowledge/rules.js +217 -24
  17. package/dist/knowledge/styleResolve.js +0 -1
  18. package/dist/knowledge/styleResolveNative.js +88 -4
  19. package/dist/platform/capture/actions.js +27 -1
  20. package/dist/platform/capture/capture.js +10 -13
  21. package/dist/platform/capture/persistent-browser.js +130 -0
  22. package/dist/platform/capture/summary.js +4 -1
  23. package/dist/resources/examples/flow-list-to-form.json +8 -10
  24. package/dist/resources/examples/flow-load-table.json +1 -1
  25. package/dist/resources/examples/mobile-app-shell-drawer.json +1 -0
  26. package/dist/resources/examples/mobile-list-detail-form.json +7 -3
  27. package/dist/resources/examples/mobile-scan-code.json +285 -0
  28. package/dist/resources/guides/component.md +3 -3
  29. package/dist/resources/guides/entity.md +16 -0
  30. package/dist/resources/guides/expressions.md +8 -7
  31. package/dist/resources/guides/merge.md +98 -18
  32. package/dist/resources/guides/microservice.md +3 -3
  33. package/dist/resources/guides/middleware.md +2 -0
  34. package/dist/resources/guides/mobile-layout.md +2 -1
  35. package/dist/resources/guides/mobile-publish.md +1 -0
  36. package/dist/resources/guides/mobile-steps.md +67 -0
  37. package/dist/resources/guides/mobile.md +51 -70
  38. package/dist/resources/guides/page.md +229 -137
  39. package/dist/resources/guides/translation.md +67 -1
  40. package/dist/resources/guides/workflow.md +31 -1
  41. package/dist/resources/schema-fingerprint.json +2 -2
  42. package/dist/resources/schemas/ResolveMergeConflictsCommand.json +30 -1
  43. package/dist/tools/activities.js +3 -1
  44. package/dist/tools/applications.js +16 -2
  45. package/dist/tools/components.js +12 -6
  46. package/dist/tools/designSchemaPatch.js +18 -13
  47. package/dist/tools/entities.js +41 -4
  48. package/dist/tools/layouts.js +29 -9
  49. package/dist/tools/merges.js +29 -9
  50. package/dist/tools/mobile.js +15 -1
  51. package/dist/tools/pages.js +156 -168
  52. package/dist/tools/platformInfo.js +3 -2
  53. package/dist/tools/profileWrite.js +2 -2
  54. package/dist/tools/queries.js +1 -1
  55. package/dist/tools/rows.js +14 -3
  56. package/dist/tools/schemaOps.js +132 -34
  57. package/dist/tools/screenWrite.js +3 -3
  58. package/dist/tools/screens.js +42 -3
  59. package/dist/tools/translations.js +148 -6
  60. package/package.json +2 -2
@@ -97,11 +97,11 @@ dalından devam eder.
97
97
 
98
98
  | Global | İçerik |
99
99
  |---|---|
100
- | `Page` | `Page.user`, `Page.variables`, `Page.components`, `Page.properties`, `Page.queryParams`, `Page.authorized(roles)` |
101
- | **Komponent adları** | Her komponent adıyla doğrudan değişkendir: `specialties_table.rows = data` hem prop'u yazar hem UI'ı günceller (change event). Addaki `-` silinir: `tickets-table` → `ticketstable` |
100
+ | `Page` | `Page.user`, `Page.variables`, `Page.components`, `Page.properties`, `Page.queryParams`, `Page.authorized(roles)`, `Page.language` (etkin dil kodu, salt okunur), `Page.setLanguage(kod)` (dili değiştirir: seçim saklanır, bütün bağlar yeni dilde çözülür; projede olmayan kod yok sayılır — 2026-10-14 frontend, uzak kurulumda dağıtımdan sonra; ARC-097/098) |
101
+ | **Komponent adları** | Her komponent adıyla doğrudan değişkendir: `specialties_table.rows = data` prop'u yazar ve UI'ı günceller. Çeviriye bağlı prop **ekrandaki metinle** okunur, iç içe dizilerdekiler dahil (`menu.navigations[0].title`, `drawer.actions[0].text`); okunan diziyi aynı metinle geri yazmak çeviri bağını korur (2026-10-14 frontend, uzak kurulumda dağıtımdan sonra — öncesinde okuma `null` verirdi); `setter` adımı gibi bileşenin `change` akışını **koşturmaz** — `change` akışı yalnız kullanıcı etkileşiminde koşar (2026-10-08; öncesindeki runtime her kod yazımında `change` akışını koştururdu). Addaki `-` silinir: `tickets-table` → `ticketstable` |
102
102
  | `state.page` / `state.app` | Sayfa/uygulama state'i (oku/yaz): `state.page.selectedId = row.id`. **Bellekte yaşar** — sayfa her açıldığında tanımlı `value`'dan başlar; mobil uygulamada kalıcı olması için değişkene `storage` verilir (`platform_guide('mobile')` §Kalıcı state). **Tanımlı değişkenler de buradadır:** `variable_create` ile yazılan her `key` sayfa düzeyinde `state.page.<key>`, uygulama düzeyinde `state.app.<key>` olur; `value` nesneyse alanlarına inilir (`state.page.MyTest.isActive`) |
103
- | `Steps` | Adım çıktıları: `Steps.load_specialties` — anahtar adımın **ADIdır**, `stepId` değil (`setStorage(task.name, …)`); `Steps.<adım>.error` o adımın hatası — ama YALNIZ HTTP durum metnidir (401 için `'Unauthorized'`), ucun mesajı değil. `Steps.error` ise hata akışı için doldurulur (`onError` koşmadan önce `setStorage('error', …)` yapar) ve **yalnız orada okunur**: storage sayfa başına tek `FlowMachine`'de yaşar, ama dışarıdan başlayan her akış başlarken `'error'`'u ve kendi adımlarının önceki değerlerini temizler (`flow-engine.ts` `run`, 2026-09-25) — normal akışta `Steps.error` boştur; hata akışı ve onun tetiklediği akış (`nested`) onu görür ve şekli `{ message, code, step: { id, name, type, data } }`'dir: kullanıcıya gösterilecek metin `Steps.error.message`'tır (`err.response.data.message`, boşsa durum metni). **Başarılı bir adımda `Steps.<adım>.data` gerçek bir `Array`'dir** — `.map` · `.filter` · `.concat` · `.length` çalışır. Adım HATA dönerse aynı alan ucun hata gövdesini taşır; aşağıdaki kutuya bakın |
104
- | `self` · `item` · `Row` / `RowIndex` | Tetikleyen komponent (yazılabilir) · repeater/tablo satırı (yazmak YALNIZ o satırı günceller). Satır AKIŞINDA (satırdaki düğümün olayı) ARC-121 düzeltmesini (2026-09-27) taşıyan runtime gerekir, eskisinde boştu — sürümden bağımsız yol `<liste>.selectedItem` / `.selectedIndex`. Tekrarlayıcının KENDİ `change`/`select` akışında satır bağlamı yoktur. `item.x = …` yazımı tekrarlayıcının `change` olayı olarak iletilir: `change`'e bağlı akış her yazımda koşar, o akış da `item`'a yazarsa döngü olur |
103
+ | `Steps` | Adım çıktıları: `Steps.load_specialties` — anahtar adımın **ADIdır**, `stepId` değil (`setStorage(task.name, …)`); `Steps.<adım>.error` o adımın hatası: `{ status, message, body }` — `message` ucun kendi mesajı (`err.reason`, boşsa HTTP durum metni), `status` HTTP kodu, `body` ucun hata gövdesi (`microservice.ts` `.catch`; tablo `platform_guide('page')`). `Steps.error` ise hata akışı için doldurulur (`onError` koşmadan önce `setStorage('error', …)` yapar) ve **yalnız orada okunur**: storage sayfa başına tek `FlowMachine`'de yaşar, ama dışarıdan başlayan her akış başlarken `'error'`'u ve kendi adımlarının önceki değerlerini temizler (`flow-engine.ts` `run`, 2026-09-25) — normal akışta `Steps.error` boştur; hata akışı ve onun tetiklediği akış (`nested`) onu görür ve şekli `{ message, code, step: { id, name, type, data } }`'dir: kullanıcıya gösterilecek metin `Steps.error.message`'tır (`err.response.data.message`, boşsa durum metni). **Başarılı bir adımda `Steps.<adım>.data` gerçek bir `Array`'dir** — `.map` · `.filter` · `.concat` · `.length` çalışır. Adım HATA dönerse `Steps.<adım>.data` `null`'dır, gövde `.error.body`'dedir; aşağıdaki kutuya bakın |
104
+ | `self` · `item` · `Row` / `RowIndex` | Tetikleyen komponent (yazılabilir) · repeater/tablo satırı (yazmak YALNIZ o satırı günceller). Satır AKIŞINDA (satırdaki düğümün olayı) ARC-121 düzeltmesini (2026-09-27) taşıyan runtime gerekir, eskisinde boştu — sürümden bağımsız yol `<liste>.selectedItem` / `.selectedIndex`. Tekrarlayıcının KENDİ `change`/`select` akışında satır bağlamı yoktur. `item.x = …` yazımı da aynı kuraldadır: o satırı günceller, tekrarlayıcının `change` akışını koşturmaz |
105
105
  | `event` | `{name, data, source: {id, props}}` |
106
106
  | `cookie` | `cookie.get(name)` / `cookie.set(name, value, {expiresInHours, path})` / `cookie.remove(name)` — tarayıcı cookie'si |
107
107
  | `setComponentProperty(name, key, value)` / `getComponentProperty(name, key)` | fonksiyon alternatifi |
@@ -116,9 +116,10 @@ dalından devam eder.
116
116
  > **`.map is not a function` bir dizi sözleşmesi sorunu DEĞİL, düşmüş bir adımdır.**
117
117
  > Ölçüldü 2026-09-05 (`f09-repro/probe`, yerel kurulum): başarılı bir microservice adımında
118
118
  > `Steps.<adım>.data` `[object Array]`'dir, `Array.isArray` true döner ve `.map`/`.filter`/
119
- > `.concat` çalışır. Adım hata döndüğünde (401, 500, geçersiz parametre) motor aynı alana ucun
120
- > **hata gövdesini** yazar — o bir nesnedir, dizi metodları yoktur ve
121
- > `(Steps.x.data || []).map is not a function` tam olarak burada çıkar. 2026-09-05 koşusunda
119
+ > `.concat` çalışır. Adım hata döndüğünde (401, 500, geçersiz parametre) eski runtime aynı alana ucun
120
+ > **hata gövdesini** yazıyordu — o bir nesnedir, dizi metodları yoktur ve
121
+ > `(Steps.x.data || []).map is not a function` tam olarak burada çıkıyordu. Bugünkü runtime `data`'yı
122
+ > `null` yazar ve gövdeyi `Steps.<adım>.error.body`'ye taşır (`|| []` koruması artık yeter). 2026-09-05 koşusunda
122
123
  > boş kalan şirket filtresinin sebebi buydu: adım korumalı bir uca 401 ile düşmüştü.
123
124
  > **Teşhis sırası:** önce `Steps.<adım>.error`'a bak, sonra ifadeyi suçla. Bir dizi bekliyorsan
124
125
  > `Array.isArray(Steps.<adım>.data)` ile koru; boş listeye düşmek, hata gövdesi üzerinde
@@ -17,11 +17,12 @@ onaylıdır**. Buradaki tool'ların hiçbiri production'a yazmaz.
17
17
  environment_create {projectId, name} → 202, dal "creating"
18
18
  environment_list {projectId} → state "ready" olana kadar (30-60 sn)
19
19
  … tenant tool'larını environment: "<dal>" ile çağır (page_schema_patch, entity_patch, …)
20
- merge_plan {projectId, sourceEnvironmentId} → 202, plan "planning"
20
+ merge_plan {projectId, sourceEnvironmentId, title} → 202, plan "planning" (title ZORUNLU)
21
21
  merge_get {projectId, planId} → status "planned" olana kadar
22
22
  merge_get {projectId, planId, view:"conflicts"} → çakışanları oku
23
- merge_resolve{projectId, planId, resolutions[]} → her çakışmaya source|target seç
24
- merge_apply {projectId, planId, message} → 202 (message ZORUNLU)
23
+ merge_get {projectId, planId, checks:true} → merge öncesi doğrulama (uyarılar)
24
+ merge_resolve{projectId, planId, resolutions[]} → source | target | both | custom
25
+ merge_apply {projectId, planId} → 202
25
26
  merge_get {projectId, planId} → status "applied"
26
27
  environment_list {projectId} → dal listede yok, şeması düştü
27
28
  ```
@@ -94,7 +95,9 @@ değişkenler, çözülen ref'ler) kareyi tamamlar: kare ne çizildiğini, bu ik
94
95
  - `kind`: `add` | `update` | `delete` | `conflict`
95
96
  - `dataRisk`: entity kaleminde veri riski var — **sebep `dataRiskReasons[]` içindedir**
96
97
  - `derivedFromKey`: kalem OTOMATİK ÜRETİLMİŞ bir kayıtsa (varsayılan DTO) kaynağının anahtarı
97
- - `elements[]`: `path`, `kind`, `base`, `source` (dal), `target` (development), `resolution`
98
+ - `elements[]`: `path`, `kind`, `base`, `source` (dal), `target` (development), `resolution`. Açık bir elemanda
99
+ `resolution` hiç gelmez: gateway null alanları düşürür, yani alanın yokluğu "karar verilmedi" demektir.
100
+ `ownerElementId`: yolu bileşeni adlandırmayan widget prop'unun (`widgetComponentProps[id=…]`) bileşeni
98
101
 
99
102
  `dataRisk` bir **uyarıdır, apply'ı bloklamaz**; dört sebepten biriyle doğrudur:
100
103
 
@@ -144,8 +147,83 @@ bir merge'de entity ve onun varsayılan DTO'su böylece zıt çözülemez.
144
147
  { "itemKey": "page:8f1c…", "path": "components[elementId=btn_save].props[name=label].ref", "choice": "source" }
145
148
  ```
146
149
 
147
- `path` boş bırakılırsa o kaydın **tüm** çakışmaları aynı tarafa çözülür. `source` = dal,
148
- `target` = development.
150
+ `path` boş bırakılırsa o kaydın **tüm** çakışmaları aynı karara çözülür.
151
+
152
+ | `choice` | Ne yazılır | Ne zaman |
153
+ |---|---|---|
154
+ | `source` | dalın değeri | dal haklı |
155
+ | `target` | development'ın değeri | development haklı |
156
+ | `both` | iki tarafın birleşimi: development'ın listesi + dalın ekledikleri − dalın sildikleri. Development aynı öğeyi zaten eklediyse ikinci kez eklenmez | yalnız `combinable: true` elemanda (kimliksiz ya da gruplu liste); skalerde 400 |
157
+ | `custom` | `value`'daki değer | iki taraf da tam doğru değil. `value` ZORUNLU, türü `source`/`target` ile aynı olmalı. Kök yolda kaydın gövdesidir, `order-conflict`'te kimlik listesidir. Yolsuz `custom` yalnız kaydın tek çakışması varken kabul edilir |
158
+
159
+ ```json
160
+ { "itemKey": "page:8f1c…", "path": "components[elementId=btn_save].props[name=label].ref", "choice": "custom", "value": "Siparişi kaydet" }
161
+ ```
162
+
163
+ Varsayılan DTO (`derivedFromKey`) ve alanları (`derivedFromPath: fields[id=…]`) kaynağının kararını kendiliğinden
164
+ alır. `both` kendi listesiyle yeniden birleştirilir, `custom` yalnız üç taraf aynıysa geçer. Kaynağına zıt
165
+ çözmek 400 döner.
166
+
167
+ Sayfa/layout/widget'ın **dizi prop'u** (ör. dropdown `options`) planda iki kez görünür: okunur liste `….ref` ve
168
+ runtime'ın çizdiği satırlar `….children`. **Kararı `ref`'e verin**; `children` onu kendiliğinden izler. Bir taraf
169
+ seçimi aynen geçer; `both` ve `custom` satırlara listeden yeniden kurulur. `children`'a ayrı ve farklı karar vermek
170
+ ekranda listenin değil satırların çizilmesine yol açar.
171
+
172
+ **Nesne prop'unda** (butonun `icon`'u, `surfaceStyle`) aynı kural anahtar düzeyindedir: anahtarın değeri
173
+ `….ref.<anahtar>…`, runtime'ın çizdiği satırı `….children[name=<anahtar>]…`. İki taraf aynı anahtara farklı yazdıysa
174
+ ikisi de çakışma gelir; **kararı `ref.<anahtar>`'a verin**. Satır karar istemez ve `summary.unresolved`'a girmez:
175
+ merge onu değerin birleşik hâlinden kurar (`custom` dahil). Farklı anahtarlara yapılan değişiklikler kendiliğinden birleşir. Koşu 46 S-9:
176
+ iki ayrı karar kabul ediliyor, sayfa satırınkini çiziyordu.
177
+
178
+ Bir taraf prop'un **tipini** değiştirdiyse (`valueType`: `static` → `code`) ve öbür taraf da tipine ya da değerine
179
+ dokunduysa (tipler farklı kalır) eleman prop'un kendisidir: yol `…props[id=…]` (widget'ta `widgetComponentProps[id=…]`), `base`/`source`/`target` prop gövdesi,
180
+ `combinable: false`. Tip ile değer tek karardır; ayrı karar verilseydi düz metin kod olarak çalışabilirdi.
181
+ `custom`'da `value` prop gövdesidir, kimliği sunucu yoldan yazar; statik listenin satırlarını (`children`) da `ref`'ten
182
+ sunucu kurar, gönderilen `children` kullanılmaz:
183
+
184
+ ```json
185
+ { "itemKey": "page:8f1c…", "path": "components[elementId=btn_save].props[id=01a1…]", "choice": "custom",
186
+ "value": { "name": "text", "type": "primitive", "valueType": "code", "ref": "{{ 'Kaydet ' + Page.variables.adet }}", "children": [] } }
187
+ ```
188
+
189
+ Kopyada `target`, dalın kopyasından türemiş DTO alanını da düşürür. `source` ise development'ın ikizinden türemiş
190
+ DTO alanını siler.
191
+
192
+ ### Olası kopya: `kind: "duplicate"`
193
+
194
+ İki taraf **aynı içeriği farklı kimlikle** eklediyse (iki `telefon` alanı, aynı girişi taşıyan iki bileşen) eleman
195
+ `duplicate` gelir; `twinPath` development'taki ikizdir. `combinable: true` olan kopya **engellemez**: çözümsüz
196
+ bırakılırsa ikisi de yazılır (git'in varsayılanı).
197
+
198
+ Kopyalar **`slug` taşıyorsa** (tablo alanı = kolon, sayfa = rota) ikisi birden kalamaz: eleman `combinable: false`
199
+ gelir, `summary.unresolved`'a sayılır ve birini seçene kadar apply edilmez; `both` 400 döner. Gerçek uçta ikisini
200
+ tutan apply "column already exists" ile düştü.
201
+
202
+ - `target`: development'ınki kalır, dalın eklemesi düşer.
203
+ - `source`: dalınki kalır, development'ın ikizi çıkar.
204
+ - `both`: ikisi de kalır (yalnız `combinable: true`).
205
+ - `custom`: tek öğe yazılır. `value` öğenin gövdesidir; kimliği development'ınkine çevrilir.
206
+
207
+ Development tabanda **zaten olan** bir alanı dalın eklediği slug'a yeniden adlandırdıysa dalın alanı da kopya gelir:
208
+ `twinPath` yeniden adlandırılan alandır ve **`base` doludur** (o alanın taban hâli). Bu kopyada `source` 400 döner —
209
+ development'ın alanı veri taşır, dalınki onun yerine geçemez; `target` (dalınki düşer) ya da `custom` seçin. Koşu 46
210
+ S-8: ikisi de yazılıyor, apply kolon çakışmasıyla düşüyordu.
211
+
212
+ Ayarı farklı, aynı adlı iki öğe kopya **sayılmaz**; ikisi ayrı eklenir ve doğrulamada `name-clash` uyarısı olarak
213
+ görünür. İki istisna: **tablo alanında slug** (kolon adı) ve **bileşen prop'unda ad** tekildir. Ayarı farklı olsa da
214
+ aynı slug'lı alan ya da aynı adlı prop eklemesi `combinable: false` kopyadır. Aynı adlı ama slug'ı farklı iki alan
215
+ yine ayrı eklenir.
216
+
217
+ ### Merge öncesi doğrulama: `merge_get {checks: true}`
218
+
219
+ | Kod | Seviye | Anlamı |
220
+ |---|---|---|
221
+ | `unresolved` | error | çözülmemiş çakışma ya da `slug` taşıyan çözümsüz kopya; apply 409 döner. Dizi prop'unun `children`'ı `ref` açıkken, nesne prop'unun satırı (`children[name=…]`) değeri çakışırken, türetilmiş DTO elemanı kaynağı açıkken ayrı satır almaz ve `summary.unresolved`'a girmez: kaynağı çözünce kendiliğinden çözülür |
222
+ | `duplicate-open` | warning | çözümsüz kopya (`combinable: true`), ikisi de yazılacak |
223
+ | `name-clash` | warning | birleşik gövdede aynı adla iki kardeş (örneğin iki `telefon` sütunu) |
224
+ | `dangling-reference` | warning | merge'ün sildiği ya da kopya kararında düşen bir kimliğe hâlâ başvuru var |
225
+
226
+ Uyarılar apply'ı engellemez; ama okunmadan `merge_apply` çağrılmaz.
149
227
 
150
228
  `merge_resolve` **özet döner** — `{ planId, status, summary, resolved }`. Planın kendisi yanıtta
151
229
  yoktur; kalan çakışmalar için `merge_get {planId, view:"conflicts"}` çağırın.
@@ -156,30 +234,31 @@ yoktur; kalan çakışmalar için `merge_get {planId, view:"conflicts"}` çağı
156
234
  | Durum | Kod | Ne yapmalı |
157
235
  |---|---|---|
158
236
  | Çözülmemiş çakışma var | 409 | `merge_get view:"conflicts"` → `merge_resolve` → tekrar `merge_apply` |
159
- | Development plan alındığından beri değişti | 409 | `merge_plan` ile **yeniden planla**; eski planı uygulama |
237
+ | Dal ya da development plan alındığından beri değişti (`isStale`) | 409 | `merge_refresh`; değişmeyen elemanların kararları korunur, değişenler yeniden sorulur |
160
238
  | Plan 24 saatten eski | 400 | yeniden planla |
161
239
 
162
240
  Aynı `merge_apply` çağrısını tekrarlamak sonucu değiştirmez — 409 geçici bir hata değildir.
163
241
 
164
- ## Mesaj zorunludur
242
+ ## Başlık zorunludur
165
243
 
166
- `merge_apply` mesajsız çalışmaz. Mesaj plana yazılır, plan uygulandıktan sonra da durur ve
167
- geçmişte bu mesajla görünür — git'teki merge commit mesajının karşılığıdır.
244
+ İsteğin adı **plan açılırken** `merge_plan`'ın `title` alanında verilir; `merge_apply` ve `merge_revert` ayrı bir
245
+ mesaj almaz. Başlık plana yazılır, uygulandıktan sonra da durur ve geçmişte bu adla görünür — git'teki merge
246
+ request başlığının karşılığıdır.
168
247
 
169
- - Boş ya da yalnız boşluktan oluşan mesaj **şema katmanında** reddedilir; istek platforma hiç gitmez.
170
- - En fazla 200 karakter.
248
+ - Boş ya da yalnız boşluktan oluşan başlık **şema katmanında** reddedilir; istek platforma hiç gitmez.
249
+ - En fazla 100 karakter.
171
250
  - Kalıp: `<dal> → development: <amaç>`. Ne yaptığını yaz, "merge" yazma.
172
251
 
173
- Gateway senin yerine mesaj **uydurmaz**: ne değiştirdiğini bilen sensin.
252
+ Gateway senin yerine başlık **uydurmaz**: ne değiştirdiğini bilen sensin.
174
253
 
175
254
  ## Geri almak
176
255
 
177
256
  Uygulanmış bir merge silinmez, **ters bir planla** geri alınır:
178
257
 
179
258
  ```
180
- merge_revert {projectId, planId, message} → 202, ters plan planning
259
+ merge_revert {projectId, planId} → 202, ters plan planning
181
260
  merge_get {projectId, planId: <ters plan>} → status planned olana kadar
182
- merge_apply {projectId, planId: <ters plan>, message}
261
+ merge_apply {projectId, planId: <ters plan>}
183
262
  ```
184
263
 
185
264
  Bittiğinde ters plan `applied`, kaynak plan `reverted` olur; ikisi de geçmişte durur.
@@ -220,7 +299,7 @@ merge_list {projectId, environmentId:"<env>"} → o environment'ı kaynak ya d
220
299
  ```
221
300
 
222
301
  `items[]` bu uçta **her zaman boştur** — bir planın değişiklik listesi için `merge_get`.
223
- Satırda `message`, `createdBy` (planı alan), `appliedBy` (uygulayan), `appliedAt` ve
302
+ Satırda `title`, `description`, `createdBy` (planı alan), `appliedBy` (uygulayan), `appliedAt` ve
224
303
  `summary` sayaçları gelir. Sayfalama `pageIndex` / `pageLength` ile.
225
304
 
226
305
  Bir kaydın neden değiştiğini ararken sıra: `merge_list status:"applied"` → ilgili satırın
@@ -262,5 +341,6 @@ plan `applied` kalır.
262
341
  - Merge sürerken dal ve development'a yazma kilitlidir; **diğer dallar etkilenmez**.
263
342
  - Proje başına açık dal sayısı sınırlıdır (varsayılan 5) — sınır bağlantı bütçesidir, ürün kararı
264
343
  değil.
265
- - Dal adı `^[a-z][a-z0-9_]{2,30}$` kuralına uyar ve şema adı olur; `development`, `production`,
266
- `public`, `pg_*` gibi adlar rezervedir.
344
+ - Dal adı küçük harfle başlar; küçük harf/rakam/alt çizgi/tire ve `/` ile ayrılmış parçalardan oluşur
345
+ (ör. `feature/odeme`), 3-31 karakterdir ve şema adı olur. Adres parçası (slug) `/` yerine `-` taşır;
346
+ `development`, `production`, `public`, `pg_*` gibi adlar rezervedir.
@@ -25,7 +25,7 @@ söyler. Hiçbir kural zorlamaz — ucun gerçek bir tuzağı yoksa alanı hiç
25
25
  - **Sessiz boş dönüş** — "filtre eşleşmezse 404 değil boş dizi döner"
26
26
 
27
27
  ```jsonc
28
- { "name": "urun-listesi", "httpMethod": "Get",
28
+ { "name": "urun-listesi", "httpMethod": "get",
29
29
  "description": "Aktif ürünleri ada göre sıralı döner.",
30
30
  "caution": "Sayfalama yok; katalog büyüdükçe yanıt boyutu doğrusal büyür." }
31
31
  ```
@@ -534,8 +534,8 @@ hem `joins` ile getirin hem `selectFields`'a koyun; ondan sonra `filters[0][fiel
534
534
  > 2026-09-06 koşusunda dokuz uç kuruldu ve dokuz patch çağrısının dokuzu da yalnız bu yolu yazdı
535
535
  > (~3 dk). Alan create gövdesinde zaten kabul ediliyor.
536
536
 
537
- > Sayfa tarafındaki karşılığı: kullanıcı ekrandan süzüyorsa (`table` + `filterMode` açık, ya da
538
- > `datatable` + `isFilterable` kolonlar) uç `enableDynamicFilters: true` olmalı; komponent seçilen
537
+ > Sayfa tarafındaki karşılığı: kullanıcı ekrandan süzüyorsa (`datatable` + `isFilterable`
538
+ > kolonlar) uç `enableDynamicFilters: true` olmalı; komponent seçilen
539
539
  > koşulları kendi `filters` prop'una yazar ve akış onu uca `filters` parametresi olarak geçirir.
540
540
  > Süzme yoksa statik query-string filtreleri yeterlidir — bkz. `platform_guide('page')` Kural 1e.
541
541
 
@@ -297,6 +297,8 @@ değişkenlerini `Page.variables` olarak taşır; tarayıcıda ve cihazda okunur
297
297
  **`rule.isPrivate: true`** ver (Studio: "Sunucuda tut"). Aynı istekteki sonraki middleware'ler
298
298
  onu okumaya devam eder; istemciye gitmez. Sayfanın akışları istemcide koşar ve `Page.variables`'ı
299
299
  okur — işaretli değişken orada `undefined`'dır; akışın ihtiyaç duyduğu alanı işaretleme.
300
+ `IsPrivate` değişken Studio otomatik tamamlamasında önerilmez ve `workflow_middleware_variables`
301
+ çıktısında da yer almaz (sonraki middleware'in editörü dahil).
300
302
  - İstemcinin gerçekten ihtiyaç duyduğu alanları ayrı, işaretsiz bir değişkende **izin listesiyle**
301
303
  kur (yukarıdaki `settings`). Aynı ad sonra işaretsiz bir adımla yeniden yazılırsa istemciye açılır.
302
304
  - Yalnız microservice uçlarına bağlı middleware'de değişken yanıtla dönmez; işaret yine de zararsızdır.
@@ -7,7 +7,8 @@ Bir mobil ekran kurmadan önce bunu okuyun. Buradaki kurallar zevk meselesi değ
7
7
  - **Yazımda uyarı.** Yazım yanıtında sözleşme dışı durum `MOB-015…021` olarak gelir (`layoutWarnings`).
8
8
  - **Çizimde ölçüm.** `page_screenshot({ layoutReport: true })` sonucu sayıyla ölçer (`layoutSummary.alignment`).
9
9
 
10
- Temel mobil bilgi (flex-only yerleşim, tipli stil, gezinme, adımlar) için `platform_guide('mobile')`.
10
+ Temel mobil bilgi (flex-only yerleşim, tipli stil, gezinme) için `platform_guide('mobile')`, cihaz adımları için
11
+ `platform_guide('mobile-steps')`.
11
12
  Altın örnek: `ngapps://examples/mobile-form-screen`.
12
13
 
13
14
  ## Sözleşme
@@ -71,6 +71,7 @@ metni yeni numarayla yeniden dener.
71
71
  | P-11 | push (yalnız `testflight` / `internal`): `warn push_incomplete` (kimlik eksik — derleme push'suz çıkar); `push_unreadable` (kimliğin şifresi çözülemiyor), `push_profile_stale` (profil APNs anahtarından önce üretilmiş), `push_firebase_mismatch` (`google-services.json` başka Firebase projesinin), `push_package_mismatch` (dosya yüklendikten sonra Android paket adı değişmiş), `push_shell_outdated` (kabuk paketi push taşımıyor) | Studio › Yayın › Hesaplar ve kimlik bilgileri (insan yükler); `push_profile_stale`: profili yeniden üret; `push_shell_outdated`: platform ayarı (destek). Push kapalı ya da hazırsa satır `ok`'tur ve dryRun'da görünmez — hazır olduğunu `mobile_store_get { manifest: true }` → `manifest.push` söyler |
72
72
  | P-12 | izinler: `warn permissions_shell_outdated` (yayındaki kabuk izin kümesini taşımıyor: sabit izinler bildirilir, kamera çalışmaz, kipler uygulanmaz — başka P-12 satırı çıkmaz); kabuk taşıyorsa `fail permission_off_in_use` (kip `off` ama production o izni yazılı olarak kullanıyor) ya da `warn launch_permissions_shell_outdated` (açılışta sorulan izin var, kabuk açılışta sormuyor; kabuk kümeyi de taşımıyorsa bu cümle ilk uyarının iletisine eklenir) | `mobile_store_update { settings: { permissionModes } }` ya da kullanımı kaldırıp dağıtım; açılış uyarısında daha yeni kabuk ya da izni akış adımından isteme |
73
73
  | P-13 | reklam (yalnız `ads.enabled`): `warn ads_app_id_missing` (bu platformun AdMob App ID'si yok — derleme reklamsız çıkar), `warn ads_shell_outdated` (yayındaki kabuk reklam taşımıyor), yükleme hedefinde `manual ads_test_mode` (test kipi açık: mağazada test reklamı görünür), değilse `ok ads_ready` (yerleşim sayısıyla) | `mobile_store_update { settings: { ads } }`; test kipini yayından önce `testMode: false` |
74
+ | P-14 | kod tarama (yalnız production akışlarında `scan-code` adımı varsa): `warn scan_shell_outdated` (yayındaki kabuk tarayıcıyı taşımıyor — derleme çıkar, adım `error` dalıyla biter); taşıyorsa `ok scan_ready`. Kamerayı yalnız dosya alanı kullanıyorsa satır hiç gelmez | yeni kabuk paketi (destek); ajan düzeltemez |
74
75
 
75
76
  Satırın `message` ve `fixHint`'i backend'den, istek dilinde gelir.
76
77
 
@@ -0,0 +1,67 @@
1
+ # Mobil akış adımları rehberi
2
+
3
+ Mobil uygulamada (`type:'mobile'`) cihaza ve barındırıcıya dokunan akış adımları: mobildeki davranış, argüman
4
+ sözleşmesi, `Steps.<adım>` sonucu ve tarifler. Uygulamanın kendisi (kurulum, bileşenler, gezinme, kalıcı state,
5
+ uzak push, sayfanın `mount` / `focus` olayları) `platform_guide('mobile')`'dedir.
6
+
7
+ ## Adım sözlüğü
8
+
9
+ Motorun kendi adımları (`setter`, `condition`, `microservice`, `code`, `function`, `flow-trigger`,
10
+ `validation`) iki platformda aynıdır. Barındırıcıya dokunan adımlar:
11
+
12
+ | Adım | Mobilde |
13
+ |---|---|
14
+ | `redirect` | yığına iter (göreli yol) ya da dışarıda açar (tam adres) |
15
+ | `navigate` | eski mobil şemaların adı — `redirect`'in takma adıdır; gateway `redirect`'e çevirir |
16
+ | `alert` | tipli uyarı (`success`/`error`/`info`/`warning`) toast'tur ve beklemez; düz uyarı onay bekler. `message`/`description` metin şablonudur (`"{{ t('kayit.tamam') }}"`; `platform_guide('page')` §alert). 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 }`) |
17
+ | `confirm` | `yes` / `no` dalları; `message`/`title`/`confirmText`/`cancelText` metin şablonudur (`{{ }}`, çeviri `t('anahtar')`) |
18
+ | `download` | adres cihazın açma sayfasında açılır; ikili gövde (JSON değil) önbelleğe dosya olarak yazılıp paylaşım sayfasında (`expo-sharing`) açılır, Studio mobil önizlemesi dosyayı indirir (`MOB-005`, bilgi) |
19
+ | `storage` | `args.name` `state.page.<key>` / `state.app.<key>` ise o state'i yazar (`platform_guide('mobile')` §Kalıcı state), değilse cihaz deposuna |
20
+ | `setcookie` | güvenli cihaz deposuna (SecureStore) yazar; değer sonraki her istekte `X-Flow-Cookie` başlığıyla gider ve middleware/workflow onu web'deki gibi `cookie.<ad>` olarak okur — `Cookie` başlığıyla gitmez, çünkü iOS onu yerel çerez kavanozuyla virgülle birleştirip bozuyordu (`MOB-005`, bilgi); aynı adlı sunucu çerezi varsa o kazanır. Studio paleti mobilde de sunar |
21
+ | `share` | RN paylaşım sayfası (metin + bağlantı; Android'de bağlantı metne eklenir, `title` iOS'ta e-posta konusu, Android'de seçici başlığı); akış `always` ile sürer — iOS'ta sayfa kapanınca, Android'de seçici açılır açılmaz (RN kapanışı bildirmez). Argümanlar `platform_guide('page')` §`share` adımı |
22
+ | `haptic` | dokunsal geri bildirim (`expo-haptics`, izin istemez). `args.value` stil, **düz metin**: `success` · `warning` · `error` · `light` · `medium` · `heavy` · `selection`; boş → `medium`. Değer yazmaz, hep `always`. Web'de titreşim varsa kısa titreşim, yoksa hiçbir şey (paletten yalnız mobilde sunulur) |
23
+ | `location` | cihaz konumu (`expo-location`; ilk kullanımda izin sorulur; derleme konumu taşımıyorsa sorulmaz, `error` dalı `status: 'unavailable'`). `args.value` doğruluk, düz metin: `low` · `balanced` · `high` (boş → `balanced`); `args.isRaw: true` → adres çözülmez. Sonuç `Steps.<adım>.data = { latitude, longitude, accuracy, address }`, `address = { country, region, subregion, city, district, street, postalCode }` ya da `null` (Türkiye'de il `region`, ilçe `subregion`, yoksa `city`). İzin reddi `Steps.<adım>.error = { status: 'denied', message }`, okunamayan konum `unavailable`: adımın `error` dalı bağlıysa o koşar (`always` koşmaz), yoksa akışın hata akışı, o da yoksa `always` (`data: null`). Web'de tarayıcı konumu, adres hep `null` |
24
+ | `notification-permission` | bildirim izni ister (sorulmuşsa yeniden sormaz, kalıcı red yeniden sorulmaz). Sonuç `Steps.<adım>.data.status`: `granted` · `denied` · `undetermined`; red hata değildir, hep `always` |
25
+ | `notification-schedule` | yerel bildirim planlar (`expo-notifications`; uzak push değil). `args.name` kimlik ve `args.title` başlık **metin şablonudur** — yalnız `{{ }}` içi çözülür (`'ilac-{{ item.id }}-0800'` kayıt başına ayrı kimlik, `"{{ t('ilac.vakti') }}"` çeviri; `{{ }}` dışındaki ifade çözülmez, FLOW-022); aynı kimlik eskisinin yerini alır. `args.message` gövde (**ifade**), `args.value` zaman (**ifade**): sonucu `'HH:mm'` → her gün, `{ weekday: 1–7, time: 'HH:mm' }` → her hafta (1 = Pazartesi), ISO tarih-saat → bir kez. `args.path` dokununca açılacak sayfanın slug'ı, düz metin — şablon çözmez (+ `args.query` ifade), boşsa uygulama açılır. İzin yoksa `error.status: 'denied'`, geçmiş ya da okunamayan zaman `invalid`: `error` dalı → hata akışı → `always`. Web'de desteklenmez (`unsupported`) — sayfa kapalıyken tarayıcı gösteremez |
26
+ | `notification-cancel` | `args.name` kimlikli planlı bildirimi siler (planlamadaki gibi metin şablonu: `'ilac-{{ item.id }}-0800'`), `'*'` hepsini; hep `always` |
27
+ | `permission` | bir cihaz iznine bakar ya da ister. `args.value` izin türü, **düz metin — ifade olamaz** (mağaza derlemesinin taşıyacağı izinler bu değerden, akış koşmadan hesaplanır; `FLOW-038`); `args.name` kip: `check` yalnız okur (sistem penceresi açılmaz), `request` verilmemiş ve hâlâ sorulabilen izni ister (boş → `request`). Sonuç `Steps.<adım>.data = { status, canAskAgain }`: `granted` · `denied` · `undetermined` · `unavailable` (derleme bu izni taşımıyor ya da web önizlemesi); `canAskAgain: false` → yalnız Ayarlar'dan değişir. `tracking` (iOS App Tracking Transparency) sonucu ayrıca `advertisingId` taşır: izin `granted` iken IDFA (iOS) / AAID (Android), değilse `null`; oynatıcı `tracking`'i hiç taşımaz (`unavailable`). Red hata değildir, hep `always`. Türler: `mobile_store_get { detail: ['permissions'] }` (`support: supported` satırlarının `key`'i) |
28
+ | `open-settings` | uygulamanın sistem ayar sayfasını açar; `Steps.<adım>.data.opened`. Argüman almaz |
29
+ | `back` | gezinme yığınında bir geri (iOS geri kaydırmasıyla aynı sonuç; kök ADR 0013). Argüman almaz (yazılan okunmaz), sonuç yazmaz, hata üretmez, hep `always`. Yığının ilk ekranında hiçbir şey yapmaz; Studio önizlemesinde runtime'ın kendi gezinmesi yoksa yine hiçbir şey, web uygulamasında tarayıcının geri'sidir. Formu kaydedip önceki ekrana dönmek için `redirect` yerine bunu kullanın: `redirect` yığına yeni ekran iter, geri kaydırması kaydedilen forma döner. 2026-10-08 öncesi runtime ve backend `back`'i tanımaz |
30
+ | `ad-show` | tam ekran reklam (kök ADR 0012). `args.value` mağaza ayarındaki yerleşimin **adı**, düz metin (`FLOW-039`); biçimi `interstitial` · `rewarded` · `rewardedInterstitial` olmalı. Reklam yüklenip gösterilir, kullanıcı kapatınca `Steps.<adım>.data = { shown: true, reward }` ile sürer — `reward` ödüllü reklamı sonuna kadar izleyende `{ type, amount }`, değilse `null` (ödül ayrı dal değil, `condition` ile okunur). 10 sn'de reklam yoksa, SDK hata verirse, derleme reklam taşımıyorsa ya da rıza yoksa `error.status: 'unavailable'`; tanımsız ya da banner yerleşimi `invalid`: `error` dalı → hata akışı → `always`. Oynatıcı ve web önizlemesi her zaman `unavailable` — reklamsız dalı orada sınayın. Paletten yalnız mobilde |
31
+ | `scan-code` | tam ekran kod tarayıcı (kök ADR 0017): tek kod okur ve kapanır. `args.value` izinli türler, **virgüllü düz metin** (`FLOW-042`): `aztec` · `codabar` · `code39` · `code93` · `code128` · `datamatrix` · `ean8` · `ean13` · `itf14` · `pdf417` · `qr` · `upc_e`; boş → hepsi. `upc_a` yok: UPC-A etiketi `ean13` ve on üç haneyle (başında `0`) döner. `args.title` ekrandaki yönerge, metin şablonu (`{{ t('anahtar') }}`). Sonuç `Steps.<adım>.data`: `{ value, type, source }` — `source` `camera` ya da `manual` (ekrandaki "elle gir"; tür bilinmiyorsa `type: null`). `error` dalı: `status` `cancelled` (kullanıcı kapattı — **vazgeçme bir hatadır, `error` dalını bağla**) · `denied` (kamera izni yok) · `unavailable` (derleme kamerayı taşımıyor ya da tarayıcı zaten açık) · `unsupported` (web uygulaması) · `invalid`. Kamera izni kendiliğinden derlemeye girer (adım production'daysa). Önizleme (`/dev`, `page_screenshot`) kamera açmaz, "kod gir" penceresi çizer: `__scan-value`'ya yaz, `__scan-confirm` ya da `__scan-cancel` (çok türde `__scan-type-<tür>`). `page_screenshot` pencereyi yalnız `captureUnready: true` ile sürer (akış pencereyi beklerken "koşuyor" sayılır; her eylem 15 sn bekler). Tam gövde: `ngapps://examples/mobile-scan-code` |
32
+
33
+ ## Bildirim tarifi
34
+
35
+ Bildirim adımları paletten yalnız mobil uygulamada sunulur. Tarif — S04 "her gün hatırlat": önce
36
+ `notification-permission` (S03 düğmesi), sonra `timepicker`'ın değeriyle `notification-schedule`
37
+ (`value: "<saat>.value"`, değer `HH:mm`) ve `error` dalında kullanıcıya mesaj. Çevrimdışı bandı adım değil
38
+ ifadedir: `isHidden: {{ Device.isOnline }}` (`platform_guide('expressions')`).
39
+
40
+ ## İzin tarifi
41
+
42
+ **İzin tarifi** (izin, ihtiyaç anında — düğmeye basılınca — istenir; sayfa açılışında değil): `permission`
43
+ `check` → `condition`, **bu sırayla**: `status === 'granted'` ise işe devam; `'unavailable'` ise özelliği sunma
44
+ (Ayarlar'da açılacak izin yoktur — `canAskAgain` burada da `false` gelir); `canAskAgain` ise `permission` `request`
45
+ ve sonucuna göre devam; değilse nedenini söyleyip `open-settings`. `location` adımı, `map.showUser` ve dosya alanının
46
+ kamera/galeri kaynağı izni kendileri de ister — `permission` adımı *ne zaman* sorulacağını seçmek ve sonucu
47
+ okumak içindir. `unavailable` bir hata değil, derlemenin o izni taşımadığının haberidir
48
+ (`platform_guide('mobile-publish')` §İzinler).
49
+
50
+ ## Reklam tarifi
51
+
52
+ **Reklam tarifi** (kök ADR 0012): (1) `mobile_store_update { settings: { ads: { enabled: true, iosAppId, androidAppId,
53
+ testMode: true, units: [{ name, format, ios, android }] } } }` — reklam, App ID'si olan platformun derlemesine `tracking`'i de sokar (ATT metni
54
+ gider, kip `off` olamaz); (2) banner için sayfaya `adBanner` `unit: '<banner yerleşimi>'`; (3) geçiş/ödüllü reklam için
55
+ düğme akışına `ad-show` `value: '<yerleşim>'`, `error` dalında reklamsız devam; (4) ödül `condition`:
56
+ `Steps.<adım>.data?.reward` doluysa ödülü ver. Açılış reklamı adım değil ayardır (`ads.appOpenUnit`). Sıra cihazda:
57
+ rıza formu → açılış izinleri (ATT) → SDK; iOS'ta izleme izni yoksa reklam kişiselleştirilmeden istenir. Yerleşim adının
58
+ ayarda tanımlı olduğu sayfa/akış yazımında denetlenemez — `mobile_store_get` `settings.ads.units` ve `adsWarnings`
59
+ (`MOB-026`); yayın ayrıntısı `platform_guide('mobile-publish')` §Reklamlar.
60
+
61
+ ## Konum ve harita
62
+
63
+ `style`, `accuracy`, `reverseGeocode` gibi anahtarlar sözleşmede yoktur ve platform onları sessizce düşürür
64
+ (`FLOW-029`). "Haritada aç" için adım yoktur: `map`'in `press` olayı düğüme `mapsUrl` yazar, akış
65
+ `redirect` `isRaw: true`, `path: '<harita>.mapsUrl'` ile açar. Tarif — L05 "mevcut konumumu kullan":
66
+ düğmenin `click`'i → `location` (`always` → setter `il.value = Steps.konum.data.address?.region`, `error` →
67
+ setter hata metni). Uzak push (sunucudan gelen bildirim) ayrı rehberde: `platform_guide('mobile')` §Uzak push.
@@ -37,7 +37,8 @@ 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`, `adBanner`, `grouped-list`, `floating-container`, `skeleton`, `photo-viewer`) bilgi yalnız
40
+ `audio`, `lottie`, `webview`, `map`, `adBanner`, `grouped-list`, `floating-container`, `skeleton`, `photo-viewer`,
41
+ `flip-card`, `swipeable-stack`) bilgi yalnız
41
42
  `mobileKnowledge`'tadır. Olay yükleri
42
43
  (`events[].payload`) önce mobile özgü sözleşmeden okunur (`app-bar.clickAction`, `repeater.swipe`,
43
44
  `choice-chips.change`…), yoksa web'inkinden.
@@ -115,6 +116,9 @@ mobil bileşenlere geçen notlar). Web'de karşılığı olmayan bileşende (`ap
115
116
  listesi) ve `floating-container` (ekranın 9 noktasından birine sabit kap — §Ekrana sabit içerik)
116
117
  mobile özgüdür. `scroll-view`'de ilk çocuk dışındaki doğrudan çocuklar `states.isSticky: true` ile
117
118
  kaydırırken üstte kalır (Studio panelinde alanı yok; şemaya MCP yazar).
119
+ - **Dönen kart ve kart destesi** (2026-10-08): `flip-card` (children = [ön `view`, arka `view`]; `isFlipped`,
120
+ `flip`) ve `swipeable-stack` (`repeater` şekli: [kart `view`, boş `view`], `rows`, `currentIndex`;
121
+ `swipeLeft` / `swipeRight` / `finish` yükü `event.data`) mobile özgüdür.
118
122
  - **Faz 10** (FlutterFlow D listesi): `datatable` web ikizidir — kolon biçimi, sıralama ve sayfa hesabı web'in
119
123
  modülünden, akış sözleşmesi aynı (sıralama/sayfa değişince `sortField`/`sortType`/`offset` düğüme yazılır ve
120
124
  `mount` yeniden koşar; satır eylemi `selectedRow`u yazıp eylemin akışını koşturur). Mobile özgü `variant`:
@@ -179,12 +183,12 @@ istekle gider.
179
183
  Gateway alan tipini şemadan bilemediği için uyarı metinseldir: `.value` ile biten en az üç parçalı
180
184
  yol (`urlInput.value` gibi bileşen değeri ve `fileInput1.value.value` uyarı almaz).
181
185
 
182
- Workflow'un dosya yükleme adımı yalnız `{ id }` döndürür (URL yok); görsel adresi kaydı okuyan uçtan
183
- gelir. `page_screenshot` ile listede görseli doğrulayın.
186
+ Workflow'un `file-upload` adımı da aynı nesneyi döndürür, adres `<adım>.data.absolute_path`
187
+ (`platform_guide('workflow')`). `page_screenshot` ile listede görseli doğrulayın.
184
188
 
185
189
  ## Liste ve boş durumu
186
190
 
187
- Mobil `repeater`'ın çocukları TAM İKİ `view`'dır (web listesi tek şablonludur, bu şekil YALNIZ mobildir) — satır şablonu ve boş görünüm (ADR
191
+ Mobil `repeater`'ın çocukları TAM İKİ `view`'dır (web'de tek şablon) — satır şablonu ve boş görünüm (ADR
188
192
  docs/adr/0005; başka şekil 422 `fixed-views`). Satır yokken boş görünüm bir kez, tam genişlikte
189
193
  çizilir; boş ekranı oraya tasarla. `emptyTitle`/`emptyDescription`/`emptyIcon` prop'ları ve kardeş
190
194
  `emptystate` yolu 2026-09-30'da kaldırıldı. Mobil `view` çocuklarını sütunda dizer. Tam gövde:
@@ -204,15 +208,15 @@ bölümleri bileşen render anında çıkarır.
204
208
  3. **Çocuklar konumsaldır:** çocuk 0 bölüm başlığı, çocuk 1 öğe şablonu; fazlası çizilmez.
205
209
 
206
210
  ```json
207
- { "elementId": "tasks", "name": "tasks", "type": "grouped-list",
211
+ { "elementId": "tasks", "name": "tasks", "type": "grouped-list", "blocks": [],
208
212
  "props": { "rows": { "type": "code", "ref": "{{ q_tasks.data }}" },
209
- "groupBy": { "type": "static", "data": "dueDate" },
210
- "itemPressable": { "type": "static", "data": true } },
213
+ "groupBy": { "type": "static", "data": "dueDate" },
214
+ "itemPressable": { "type": "static", "data": true } },
211
215
  "children": [
212
- { "elementId": "day_title", "name": "day_title", "type": "text",
216
+ { "elementId": "day_title", "name": "day_title", "type": "text", "blocks": [],
213
217
  "props": { "content": { "type": "code", "ref": "{{ Section.title }} · {{ Section.count }}" } } },
214
- { "elementId": "task_row", "name": "task_row", "type": "container", "props": {},
215
- "children": [ { "elementId": "task_title", "name": "task_title", "type": "text",
218
+ { "elementId": "task_row", "name": "task_row", "type": "container", "props": {}, "blocks": [],
219
+ "children": [ { "elementId": "task_title", "name": "task_title", "type": "text", "blocks": [],
216
220
  "props": { "content": { "type": "code", "ref": "{{ item.title }}" } } } ] } ] }
217
221
  ```
218
222
 
@@ -241,10 +245,10 @@ düğümü sayfa kök kabının SONUNA koyun (akıştaki yeri boş kalır).
241
245
  sayılır), `respectSafeArea` (varsayılan açık). Alt eylem çubuğu:
242
246
 
243
247
  ```json
244
- { "elementId": "action_bar", "name": "action_bar", "type": "floating-container",
248
+ { "elementId": "action_bar", "name": "action_bar", "type": "floating-container", "blocks": [],
245
249
  "props": { "anchor": { "type": "static", "data": "bottom-center" },
246
- "fullWidth": { "type": "static", "data": true } },
247
- "children": [ { "elementId": "bar", "name": "bar", "type": "container",
250
+ "fullWidth": { "type": "static", "data": true } },
251
+ "children": [ { "elementId": "bar", "name": "bar", "type": "container", "blocks": [],
248
252
  "props": { "direction": { "type": "static", "data": "row" } }, "children": [] } ] }
249
253
  ```
250
254
 
@@ -303,6 +307,18 @@ aynı katman sırası: tema taban → gruplar → serbest `style`).
303
307
  (`'15deg'` yazılmaz); aralık dışı (`rotate` ±360, `scale` 10–500, kaydırma ±1000) yazılmaz ve
304
308
  `nativeStyle.warnings`'e düşer. Katman ve kabukta (`modal`, `drawer`, `app-bar`) bu dört alan yoktur.
305
309
  - `typographyStyle` → renk, aile (tek ad), punto, kalınlık, hizalama, satır yüksekliği.
310
+ - `motionStyle` (panelde Motion; kabuğun dışında çizen `modal`, `drawer`, `photo-viewer`, `toast`, `fab`,
311
+ `floating-container` dışında her mobil bileşende) → düğümün hareketi: `enter` / `exit` (`fade`, `fadeUp`, `fadeDown`, `slideLeft`,
312
+ `slideRight`, girişte `zoomIn`, çıkışta `zoomOut`), `loop` (`pulse`, `spin`, `bounce`), `transition` (`colors`,
313
+ `opacity`, `transform`, `all`), `press` (`scale`, `opacity`), `layout` (aç/kapa), `duration` 100–2000 ms, `delay`
314
+ 0–5000 ms, `easing`. Yazılmayan alan yok sayılır (300 ms, `easeOut`; döngü kendi süresini kullanır) — varsayılanı
315
+ YAZMAYIN. Yön adı BAŞLANGIÇ tarafıdır: `fadeUp` yukarıdan, `fadeDown` aşağıdan, `slideLeft` soldan, `slideRight`
316
+ sağdan gelir. Tanınmayan alan ve küme/aralık dışı değer atlanır (`MOB-028`). `/dev`'de ve cihazda oynar: `enter` düğüm
317
+ çizilince, `exit` gizlenince (`isHidden`), `layout` bir kardeş gizlenip görününce, `transition` kabuğun stili
318
+ değişince; giriş biter, döngü sonra başlar; döngü ve `press` `surfaceStyle`'ın döndürme/ölçek/opaklığının üstüne
319
+ biner. Kanvasta donuktur (araç çubuğunda Oynat); "hareketi azalt"ta ve `page_screenshot`'ın donuk karesinde durur.
320
+ Koşullu hareket: alan da (`{ type: 'code', ref }`) grup da (`set_binding`) ifadeye bağlanır; alandaki düz metin
321
+ `{{ … }}` ifade değildir (`MOB-028`). Web şemasında yoktur.
306
322
  - Tema token'ları (`var(--ng-*)`) sayılara çözülür; proje teması değişince mobil de değişir.
307
323
 
308
324
  Çizilmeyen alanlar (`MOB-002`) ve yerine ne:
@@ -317,8 +333,9 @@ aynı katman sırası: tema taban → gruplar → serbest `style`).
317
333
  | `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` |
318
334
 
319
335
  Düğümün gerçek stilini görmek: `page_runtime_get({ applicationSlug, pageSlug, resolveStyles: true })`
320
- → her düğümde `nativeStyle: { shell, text, warnings }` (`shell` kutunun, `text` içine akan metnin
321
- stili; RN'de tipografi miras alınmaz). Yanıtta `styleResolver: 'native'`. Temanın katmanı
336
+ → her düğümde `nativeStyle: { shell, text, motion, bound, warnings }` (`shell` kutunun, `text` içine akan metnin
337
+ stili; RN'de tipografi miras alınmaz; `motion` yalnız hareketli düğümde, `motionStyle`'ın `/dev` ve cihazdaki
338
+ çözümü; `bound` ifadeye bağlı stil alanları — değerleri çalışma anında oluşur). Yanıtta `styleResolver: 'native'`. Temanın katmanı
322
339
  (`componentStyles[tip]` ← `$preset`) TABANDIR: panel grupları üstüne biner, düğümün serbest `style`'ı son söz — rapor
323
340
  da onu içerir. Mobilde tasarım sisteminin yolu `componentStyles`'tır (`theme_update`; tipin bütün düğümlerine iner).
324
341
  Mobil katalogda `style` prop'u olmadığı için düğüme yeni `$preset` başvurusu yazılamaz; kayıtlı (ör. web'den taşınmış)
@@ -376,7 +393,8 @@ etiketi ailesini `typographyStyle`'dan alır, alan etiketleri platform yazı tip
376
393
  Çekmece layout'un kök dizisindedir (`views: [shell_root, nav_drawer]`), kabuk kabının içinde değil.
377
394
  - Kabuk örnekleri: `ngapps://examples/mobile-app-shell-stack` (yığın), `ngapps://examples/mobile-app-shell-tab`
378
395
  (sekmeler), `ngapps://examples/mobile-app-shell-drawer` (çekmece); üç sayfalık liste → detay → form akışı:
379
- `ngapps://examples/mobile-list-detail-form`.
396
+ `ngapps://examples/mobile-list-detail-form`; kod tarama (düğme → `scan-code` → alana yaz):
397
+ `ngapps://examples/mobile-scan-code`.
380
398
 
381
399
  ## Çok dil
382
400
 
@@ -405,60 +423,19 @@ Mobil uygulama web ile **aynı sözlüğü ve aynı motoru** kullanır (plans/mo
405
423
  ## Akış adımları
406
424
 
407
425
  Motorun kendi adımları (`setter`, `condition`, `microservice`, `code`, `function`, `flow-trigger`,
408
- `validation`) iki platformda aynıdır. Barındırıcıya dokunan adımlar:
409
-
410
- | Adım | Mobilde |
411
- |---|---|
412
- | `redirect` | yığına iter (göreli yol) ya da dışarıda açar (tam adres) |
413
- | `navigate` | eski mobil şemaların adı — `redirect`'in takma adıdır; gateway `redirect`'e çevirir |
414
- | `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 }`) |
415
- | `confirm` | `yes` / `no` dalları |
416
- | `download` | adres cihazın açma sayfasında açılır; ikili gövde (JSON değil) önbelleğe dosya olarak yazılıp paylaşım sayfasında (`expo-sharing`) açılır, Studio mobil önizlemesi dosyayı indirir (`MOB-005`, bilgi) |
417
- | `storage` | `args.name` `state.page.<key>` / `state.app.<key>` ise o state'i yazar (§Kalıcı state), değilse cihaz deposuna |
418
- | `setcookie` | güvenli cihaz deposuna (SecureStore) yazar; değer sonraki her istekte `X-Flow-Cookie` başlığıyla gider ve middleware/workflow onu web'deki gibi `cookie.<ad>` olarak okur — `Cookie` başlığıyla gitmez, çünkü iOS onu yerel çerez kavanozuyla virgülle birleştirip bozuyordu (`MOB-005`, bilgi); aynı adlı sunucu çerezi varsa o kazanır. Studio paleti mobilde de sunar |
419
- | `share` | RN paylaşım sayfası (metin + bağlantı; Android'de bağlantı metne eklenir, `title` iOS'ta e-posta konusu, Android'de seçici başlığı); akış `always` ile sürer — iOS'ta sayfa kapanınca, Android'de seçici açılır açılmaz (RN kapanışı bildirmez). Argümanlar `platform_guide('page')` §`share` adımı |
420
- | `haptic` | dokunsal geri bildirim (`expo-haptics`, izin istemez). `args.value` stil, **düz metin**: `success` · `warning` · `error` · `light` · `medium` · `heavy` · `selection`; boş → `medium`. Değer yazmaz, hep `always`. Web'de titreşim varsa kısa titreşim, yoksa hiçbir şey (paletten yalnız mobilde sunulur) |
421
- | `location` | cihaz konumu (`expo-location`; ilk kullanımda izin sorulur; derleme konumu taşımıyorsa sorulmaz, `error` dalı `status: 'unavailable'`). `args.value` doğruluk, düz metin: `low` · `balanced` · `high` (boş → `balanced`); `args.isRaw: true` → adres çözülmez. Sonuç `Steps.<adım>.data = { latitude, longitude, accuracy, address }`, `address = { country, region, subregion, city, district, street, postalCode }` ya da `null` (Türkiye'de il `region`, ilçe `subregion`, yoksa `city`). İzin reddi `Steps.<adım>.error = { status: 'denied', message }`, okunamayan konum `unavailable`: adımın `error` dalı bağlıysa o koşar (`always` koşmaz), yoksa akışın hata akışı, o da yoksa `always` (`data: null`). Web'de tarayıcı konumu, adres hep `null` |
422
-
423
- | `notification-permission` | bildirim izni ister (sorulmuşsa yeniden sormaz, kalıcı red yeniden sorulmaz). Sonuç `Steps.<adım>.data.status`: `granted` · `denied` · `undetermined`; red hata değildir, hep `always` |
424
- | `notification-schedule` | yerel bildirim planlar (`expo-notifications`; uzak push değil). `args.name` kimlik (düz metin; aynı kimlik eskisinin yerini alır), `args.title` başlık (düz metin), `args.message` gövde (**ifade**), `args.value` zaman (**ifade**): sonucu `'HH:mm'` → her gün, `{ weekday: 1–7, time: 'HH:mm' }` → her hafta (1 = Pazartesi), ISO tarih-saat → bir kez. `args.path` dokununca açılacak sayfanın slug'ı (+ `args.query` ifade), boşsa uygulama açılır. İzin yoksa `error.status: 'denied'`, geçmiş ya da okunamayan zaman `invalid`: `error` dalı → hata akışı → `always`. Web'de desteklenmez (`unsupported`) — sayfa kapalıyken tarayıcı gösteremez |
425
- | `notification-cancel` | `args.name` kimlikli planlı bildirimi siler, `'*'` hepsini; hep `always` |
426
- | `permission` | bir cihaz iznine bakar ya da ister. `args.value` izin türü, **düz metin — ifade olamaz** (mağaza derlemesinin taşıyacağı izinler bu değerden, akış koşmadan hesaplanır; `FLOW-038`); `args.name` kip: `check` yalnız okur (sistem penceresi açılmaz), `request` verilmemiş ve hâlâ sorulabilen izni ister (boş → `request`). Sonuç `Steps.<adım>.data = { status, canAskAgain }`: `granted` · `denied` · `undetermined` · `unavailable` (derleme bu izni taşımıyor ya da web önizlemesi); `canAskAgain: false` → yalnız Ayarlar'dan değişir. `tracking` (iOS App Tracking Transparency) sonucu ayrıca `advertisingId` taşır: izin `granted` iken IDFA (iOS) / AAID (Android), değilse `null`; oynatıcı `tracking`'i hiç taşımaz (`unavailable`). Red hata değildir, hep `always`. Türler: `mobile_store_get { detail: ['permissions'] }` (`support: supported` satırlarının `key`'i) |
427
- | `open-settings` | uygulamanın sistem ayar sayfasını açar; `Steps.<adım>.data.opened`. Argüman almaz |
428
- | `ad-show` | tam ekran reklam (kök ADR 0012). `args.value` mağaza ayarındaki yerleşimin **adı**, düz metin (`FLOW-039`); biçimi `interstitial` · `rewarded` · `rewardedInterstitial` olmalı. Reklam yüklenip gösterilir, kullanıcı kapatınca `Steps.<adım>.data = { shown: true, reward }` ile sürer — `reward` ödüllü reklamı sonuna kadar izleyende `{ type, amount }`, değilse `null` (ödül ayrı dal değil, `condition` ile okunur). 10 sn'de reklam yoksa, SDK hata verirse, derleme reklam taşımıyorsa ya da rıza yoksa `error.status: 'unavailable'`; tanımsız ya da banner yerleşimi `invalid`: `error` dalı → hata akışı → `always`. Oynatıcı ve web önizlemesi her zaman `unavailable` — reklamsız dalı orada sınayın. Paletten yalnız mobilde |
429
-
430
- Bildirim adımları paletten yalnız mobil uygulamada sunulur. Tarif — S04 "her gün hatırlat": önce
431
- `notification-permission` (S03 düğmesi), sonra `timepicker`'ın değeriyle `notification-schedule`
432
- (`value: "<saat>.value"`, değer `HH:mm`) ve `error` dalında kullanıcıya mesaj. Çevrimdışı bandı adım değil
433
- ifadedir: `isHidden: {{ Device.isOnline }}` (`platform_guide('expressions')`).
434
-
435
- **İzin tarifi** (izin, ihtiyaç anında — düğmeye basılınca — istenir; sayfa açılışında değil): `permission`
436
- `check` → `condition`, **bu sırayla**: `status === 'granted'` ise işe devam; `'unavailable'` ise özelliği sunma
437
- (Ayarlar'da açılacak izin yoktur — `canAskAgain` burada da `false` gelir); `canAskAgain` ise `permission` `request`
438
- ve sonucuna göre devam; değilse nedenini söyleyip `open-settings`. `location` adımı, `map.showUser` ve dosya alanının
439
- kamera/galeri kaynağı izni kendileri de ister — `permission` adımı *ne zaman* sorulacağını seçmek ve sonucu
440
- okumak içindir. `unavailable` bir hata değil, derlemenin o izni taşımadığının haberidir
441
- (`platform_guide('mobile-publish')` §İzinler).
442
-
443
- **Reklam tarifi** (kök ADR 0012): (1) `mobile_store_update { settings: { ads: { enabled: true, iosAppId, androidAppId,
444
- testMode: true, units: [{ name, format, ios, android }] } } }` — reklam, App ID'si olan platformun derlemesine `tracking`'i de sokar (ATT metni
445
- gider, kip `off` olamaz); (2) banner için sayfaya `adBanner` `unit: '<banner yerleşimi>'`; (3) geçiş/ödüllü reklam için
446
- düğme akışına `ad-show` `value: '<yerleşim>'`, `error` dalında reklamsız devam; (4) ödül `condition`:
447
- `Steps.<adım>.data?.reward` doluysa ödülü ver. Açılış reklamı adım değil ayardır (`ads.appOpenUnit`). Sıra cihazda:
448
- rıza formu → açılış izinleri (ATT) → SDK; iOS'ta izleme izni yoksa reklam kişiselleştirilmeden istenir. Yerleşim adının
449
- ayarda tanımlı olduğu sayfa/akış yazımında denetlenemez — `mobile_store_get` `settings.ads.units` ve `adsWarnings`
450
- (`MOB-026`); yayın ayrıntısı `platform_guide('mobile-publish')` §Reklamlar.
451
-
452
- `style`, `accuracy`, `reverseGeocode` gibi anahtarlar sözleşmede yoktur ve platform onları sessizce düşürür
453
- (`FLOW-029`). "Haritada aç" için adım yoktur: `map`'in `press` olayı düğüme `mapsUrl` yazar, akış
454
- `redirect` `isRaw: true`, `path: '<harita>.mapsUrl'` ile açar. Tarif — L05 "mevcut konumumu kullan":
455
- düğmenin `click`'i → `location` (`always` → setter `il.value = Steps.konum.data.address?.region`, `error` →
456
- setter hata metni). Uzak push (sunucudan gelen bildirim) ayrı bölümde: §Uzak push.
457
-
458
- Sayfa ve layout `mount` akışları ilk çizimden sonra, `destroy` akışları sayfa kalkarken koşar.
426
+ `validation`) iki platformda aynıdır. Barındırıcıya dokunan adımlar ayrı rehberdedir:
427
+ `platform_guide('mobile-steps')` — `redirect`, `alert`, `confirm`, `download`, `storage`, `setcookie`, `share`,
428
+ `haptic`, `location`, `notification-permission` / `-schedule` / `-cancel`, `permission`, `open-settings`, `back`
429
+ ve `ad-show`'un mobildeki davranışı, argümanları, `Steps.<adım>` sonucu; bildirim, izin, reklam ve konum
430
+ tarifleri. Bu adımlardan birini yazmadan önce onu okuyun.
431
+
432
+ Sayfa ve layout `mount` akışları ilk çizimden sonra, `destroy` akışları sayfa kalkarken koşar. Yığındaki
433
+ ve sekmedeki ekran bağlı kalır: geri dönüşte `mount` KOŞMAZ. Listeyi yeniden okumak için sayfanın
434
+ **`focus`** olayı vardır (layout'ta yok): ilk görünüşte `mount`'tan sonra ve her geri dönüşte / sekme
435
+ değişiminde koşar. Bağ: `page_update` `events: [{ name: 'focus', value: <flow id> }]`. Form doldurma
436
+ `mount`'ta, liste okuması `focus`'ta — aynı okuma ikisine bağlanırsa ilk açılışta çift istek gider.
459
437
  `Functions.<ad>()` uygulama ve sayfa fonksiyonlarını görür (`platform_guide('functions')`).
460
438
 
461
-
462
439
  ## Uzak push
463
440
 
464
441
  Sunucudan gönderilen bildirim yerel `notification-schedule`'dan ayrıdır. Kanal ikiliye bağlıdır (NTF-004):
@@ -488,7 +465,8 @@ Sunucudan gönderilen bildirim yerel `notification-schedule`'dan ayrıdır. Kana
488
465
  ## Kalıcı state
489
466
 
490
467
  State varsayılan olarak **bellektedir**: sayfa her açıldığında `variable_create`'te yazılan `value`'dan
491
- başlar, akışın yazdığı değer ekran kapanınca kaybolur. Uygulama kapanıp açıldığında kaldığı yerden
468
+ başlar, akışın yazdığı değer ekran kapanınca kaybolur — `state.app` dahil: sayfalar arası
469
+ taşımak da `storage` ister (38 S-5). Uygulama kapanıp açıldığında kaldığı yerden
492
470
  sürmesi gereken değer için değişkene saklama türü verin:
493
471
 
494
472
  | `storage` | Depo | Ne için |
@@ -526,7 +504,10 @@ variable_create({ applicationId, pageId, variables: [
526
504
  780x1688 px) çeker; `'tablet'` 820x1180 @2 ya da `{ width, height, scale? }`. `viewport` verilirse
527
505
  cihaz yok sayılır (`deviceNote`). Yanıt `applicationType: 'mobile'` ve `platformNote` taşır: kare
528
506
  react-native-web çizimidir, cihazdakiyle yapısal olarak (düğüm, sıra, ölçü, renk) aynıdır.
529
- - Eylemler aynı sözlük: `click` basılabilir öğeyi (`role="button"`) bulur; `submit` runtime
507
+ - Eylemler aynı sözlük: `click` önce düğümün kendisine (basılabilirse: `role="button"`/`"switch"`/
508
+ `"combobox"`), sonra `<elementId>__press` yüzeyine, en son içteki ilk düğmeye basar — anahtar ve
509
+ onay kutusu kök kimlikle çevrilir, çoklu seçimi `<elementId>__trigger` açar (çipin "kaldır"ına
510
+ basmaz); `set` alana odaklanır, yazar ve odaktan çıkar (`changed` koşar); `submit` runtime
530
511
  köprüsüyle gider (`submitVia: 'bridge'`); sekme öğesi `__tabs__item-<sayfa slug'ı>`, çubuk `__tabs`
531
512
  özet satırıdır. `select` açılır listeyi açar ve seçeneği değerle ya da görünen metinle seçer
532
513
  (liste katmanda açılır, `optionsSeen` kaç seçenek görüldüğünü söyler); `tab` bir `tabs`