@appaflytech/zeyrek-ai-mcp 0.0.37 → 0.0.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/knowledge/componentCatalog.js +457 -156
  2. package/dist/knowledge/componentEvents.js +49 -8
  3. package/dist/knowledge/connectDataBind.js +10 -2
  4. package/dist/knowledge/expressionPrelude.js +1 -0
  5. package/dist/knowledge/mobileComponentCatalog.js +21 -9
  6. package/dist/knowledge/mobileComponentEvents.js +21 -1
  7. package/dist/knowledge/mobileStore.js +75 -0
  8. package/dist/knowledge/normalize.js +52 -17
  9. package/dist/knowledge/platform/mobile.js +1 -1
  10. package/dist/knowledge/platform/mobileRules.js +1 -1
  11. package/dist/knowledge/platform/web.js +1 -1
  12. package/dist/knowledge/rules.js +244 -25
  13. package/dist/knowledge/styleResolve.js +27 -8
  14. package/dist/knowledge/styleResolveNative.js +4 -2
  15. package/dist/knowledge/themeResolve.js +1 -1
  16. package/dist/knowledge/themeSchema.js +2 -1
  17. package/dist/observability/trace.js +7 -2
  18. package/dist/platform/capture/actions.js +24 -1
  19. package/dist/platform/capture/summary.js +4 -1
  20. package/dist/platform/secrets.js +50 -2
  21. package/dist/resources/examples/mobile-app-shell-drawer.json +1 -0
  22. package/dist/resources/examples/mobile-list-detail-form.json +3 -0
  23. package/dist/resources/guides/component.md +1 -1
  24. package/dist/resources/guides/entity.md +16 -0
  25. package/dist/resources/guides/expressions.md +7 -6
  26. package/dist/resources/guides/merge.md +5 -4
  27. package/dist/resources/guides/microservice.md +2 -2
  28. package/dist/resources/guides/middleware.md +3 -1
  29. package/dist/resources/guides/mobile-publish.md +24 -0
  30. package/dist/resources/guides/mobile.md +50 -25
  31. package/dist/resources/guides/page.md +61 -23
  32. package/dist/resources/guides/theme.md +1 -0
  33. package/dist/resources/guides/translation.md +41 -1
  34. package/dist/resources/guides/workflow.md +31 -1
  35. package/dist/resources/schema-fingerprint.json +3 -3
  36. package/dist/resources/schemas/GlobalFunctionUpdateResultDto.json +2 -1
  37. package/dist/resources/schemas/OrganizationProjectDto.json +8 -0
  38. package/dist/resources/schemas/UpsertMobileStoreSettingsCommand.json +59 -0
  39. package/dist/tools/applications.js +6 -3
  40. package/dist/tools/common.js +9 -5
  41. package/dist/tools/entities.js +41 -4
  42. package/dist/tools/environments.js +1 -1
  43. package/dist/tools/flows.js +5 -1
  44. package/dist/tools/layouts.js +22 -7
  45. package/dist/tools/mobile.js +15 -1
  46. package/dist/tools/mobileStore.js +21 -5
  47. package/dist/tools/pages.js +31 -45
  48. package/dist/tools/queries.js +1 -1
  49. package/dist/tools/rows.js +14 -3
  50. package/dist/tools/screenWrite.js +2 -2
  51. package/dist/tools/screens.js +39 -0
  52. package/dist/tools/translations.js +148 -6
  53. package/package.json +1 -1
@@ -45,7 +45,10 @@ export function domSummaryScript(viewportHeight) {
45
45
  var field = node.querySelector('input, select, textarea');
46
46
  var chosen = node.querySelector('.selected-item');
47
47
  var holder = field || chosen;
48
- var value = !owned(holder) ? '' : (field ? (field.type === 'password' ? '***' : clean(field.value)) : clean(chosen.textContent));
48
+ // 37 S-23: OTP'nin gizli girdisi type=text'tir ve kod özet satırında açık yazıyordu; sır sinyali
49
+ // tarayıcının otomatik tamamlama ipucudur (one-time-code · current-password · new-password).
50
+ var secret = !!field && (field.type === 'password' || /(^|\\s)(one-time-code|[\\w-]*-password)(\\s|$)/i.test(String(field.getAttribute('autocomplete') || '')));
51
+ var value = !owned(holder) ? '' : (field ? (secret ? '***' : clean(field.value)) : clean(chosen.textContent));
49
52
  var media = node.querySelector('img, svg, canvas, video, iframe');
50
53
  // İç içe <li> hem grubu hem alt mesajları sayıyordu: tek grup + 2 alt hata 3 görünüyordu (B-7).
51
54
  var errorItems = Array.prototype.slice.call(node.querySelectorAll('.form-validation-errors li:not(:has(li))'))
@@ -1,8 +1,12 @@
1
1
  /**
2
2
  * Desen `trace.ts`'in eski kopyasından DARALTILDI: `pass` parçası `passed`/`bypass` gibi
3
3
  * anahtarları da yakalıyordu. Günlükte zararsızdı, tool yanıtında veri kaybı olurdu.
4
+ *
5
+ * `cookie` yalnız TAM ad olarak eşleşir (g-01, 37 S-24): paketli şemalarda `StepRuleDto.cookieKey`
6
+ * çerezin ADINI taşır; parça eşleşmesi onu `***` yapıp workflow_get → update turunu bozardı (GW-001).
7
+ * `setcookie` de bilinçli dışarıda: workflow adım tipinin adıdır, sır değildir.
4
8
  */
5
- export const secretKeyPattern = /password|passwd|secret|token|authorization|api[-_]?key|credential|connection[-_]?string/i;
9
+ export const secretKeyPattern = /password|passwd|secret|token|authorization|api[-_]?key|credential|connection[-_]?string|^cookies?$/i;
6
10
  /**
7
11
  * Sırrın DEĞERİNİ değil adını, yerini ya da türünü taşıyan anahtarlar: `credentialRef`, `tokenUrl`,
8
12
  * `apiKeyHeader`, `secretFields`, `credentialKind`. Maskelenirlerse süreç tanımındaki
@@ -14,18 +18,62 @@ import { looksLikeJwt, rememberToken } from './tokenVault.js';
14
18
  const maskedValue = '***';
15
19
  const maxPaths = 20;
16
20
  const maxDepth = 12;
21
+ /**
22
+ * Ad/değer çiftinde `value`'nun yerine geçen ad (g-01, 37 S-24). Debug girdisi isteğin başlık ve
23
+ * çerezlerini `{ expression: 'Authorization', value: 'Bearer …', source, member }` olarak yazar; sırrın
24
+ * adı anahtarda değil kardeş alanda durduğu için anahtar kuralı hiçbirini görmüyordu.
25
+ *
26
+ * `type` ya da `order` taşıyan satır platform TANIMIDIR — `StepQueryParameterDto {name,value,type}`,
27
+ * `StepCustomParameterDto {name,value,order}`, `QueryParameter {name,type,value}`: değer bağlama
28
+ * ifadesidir (`input.token`), maskelenirse get → update turunda `***` geri yazılır (GW-001).
29
+ * `expression` + `value` hiçbir paketli şemada geçmez, o yüzden bu ayrımın dışındadır.
30
+ */
31
+ export function secretPairName(record) {
32
+ if (!('value' in record))
33
+ return undefined;
34
+ if (typeof record.expression === 'string')
35
+ return record.expression;
36
+ if ('type' in record || 'order' in record)
37
+ return undefined;
38
+ if (typeof record.name === 'string')
39
+ return record.name;
40
+ return typeof record.key === 'string' ? record.key : undefined;
41
+ }
42
+ /**
43
+ * Anahtarsız string yapraktaki kimlik (g-01): `eyJ…` ile başlayan JWT ve `Bearer <jeton>`.
44
+ * `maskSecretText`'ten DAR: o konsol metni içindir ve her üç parçalı noktalı adı JWT sayar —
45
+ * yanıtta `organization.something.localhost` gibi bir adı körleştirirdi. Bearer'dan sonra en az
46
+ * 16 karakter aranır; rehber metnindeki "Bearer olarak" maskelenmez.
47
+ */
48
+ export function maskSecretLeaf(text) {
49
+ return text
50
+ .replace(/Bearer\s+[A-Za-z0-9._~+/=-]{16,}/gi, `Bearer ${maskedValue}`)
51
+ .replace(/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]*/g, maskedValue);
52
+ }
17
53
  function walk(value, path, masked, refs, depth) {
18
54
  if (depth > maxDepth)
19
55
  return value;
56
+ if (typeof value === 'string') {
57
+ const safe = maskSecretLeaf(value);
58
+ if (safe === value)
59
+ return value;
60
+ if (masked.length < maxPaths)
61
+ masked.push(path);
62
+ if (looksLikeJwt(value) && Object.keys(refs).length < maxPaths)
63
+ refs[path] = rememberToken(value);
64
+ return safe;
65
+ }
20
66
  if (Array.isArray(value))
21
67
  return value.map((entry, index) => walk(entry, `${path}[${index}]`, masked, refs, depth + 1));
22
68
  if (value && typeof value === 'object') {
23
69
  const result = {};
70
+ const pairName = secretPairName(value);
24
71
  for (const [key, entry] of Object.entries(value)) {
25
72
  const child = path === '' ? key : `${path}.${key}`;
73
+ const name = key === 'value' && pairName !== undefined ? pairName : key;
26
74
  // Boole değer sır taşıyamaz: `hasExpoAccessToken: true` maskelenince ajan token'ın kayıtlı
27
75
  // olup olmadığını öğrenemiyordu (push bildirimleri g-01, 2026-10-02; GW-001 ailesi).
28
- if (secretKeyPattern.test(key) && !secretNameKeyPattern.test(key) && typeof entry !== 'boolean' && entry !== null && entry !== undefined && entry !== '') {
76
+ if (secretKeyPattern.test(name) && !secretNameKeyPattern.test(name) && typeof entry !== 'boolean' && entry !== null && entry !== undefined && entry !== '') {
29
77
  if (masked.length < maxPaths)
30
78
  masked.push(child);
31
79
  // Maskelenen değer bir JWT ise kimlik testinin yolu kapanmasın: jeton kasada tutulur ve
@@ -12,6 +12,7 @@
12
12
  "catalog": "app-bar, slot ve drawer mobil kataloğun Dalga 1 bileşenleridir. component_list mobil uygulamada bunları döndürmüyorsa katalog henüz yazılmamıştır ve layout_create 422 unknown-type alır (MOB-003).",
13
13
  "verify": "Kabuğu layout'a bağlı bir sayfada page_screenshot ile doğrula (mobilde varsayılan device: 'phone').",
14
14
  "menuTarget": "app-bar.menuTarget çekmecenin elementId'sidir (nav_drawer); iç rol app_bar__menu düğmesidir — page_screenshot actions:[{ action:'click', target:'app_bar__menu' }] çekmeceyi açar.",
15
+ "drawerButtons": "Çekmecedeki düğmeler ŞABLONDUR: mobile_app_create her sayfa için bir düğme üretir — elementId menu_<slug> (slug'daki tire ve diğer işaretler alt çizgiye döner: ana-sayfa → menu_ana_sayfa), metin pageName. Buradaki iki düğme bu örneğin kendi pages listesine (gorevler, profil) karşılıktır; başka uygulamaya kopyalanmaz (37 S-9).",
15
16
  "drawerFlows": "Menü düğmelerine akış ayrı adımda bağlanır: flow_create (host: 'layout', adım redirect { path: '<sayfa slug'ı>' }) → layout_schema_patch set_event { target: 'menu_gorevler', event: 'click' }.",
16
17
  "drawerNode": "Çekmece mobil kataloğun `drawer` bileşenidir (kind 'component', çocuk alır): layout'a doğrudan yazılır, prop'ları katalogdakilerdir (`position`, `size`, `title`, `isDismissDisabled`); kapalı doğar, `app-bar` menüsü `isOpen: true` yazarak açar. Katalogda olmayan prop (ör. `width`, `overlay`) ya da seçenek dışı değer (düğmede `variant: 'ghost'` — geçerli olanlar `solid`/`text`/`outline`) layout_create'i 422 ile düşürür.",
17
18
  "layoutContract": "Mizanpaj sözleşmesi: üst güvenli alan tek yerde, kabukta (app-bar 'safe-area' edges:'top' içinde; sayfa ikinci pay vermez). Ekran kenarı boşluğu tek değer (16) ve tek yerde: slot'un yatay ve üst margin'i — içerik app-bar'ın 16 dp altından başlar, kabuksuz sayfadaki gibi. shell_root 'shrink' ve padding'siz — düz container olduğu için tip yazılmazsa runtime 10 dp padding verir ve app-bar kenardan kopar.",
@@ -85,6 +85,7 @@
85
85
  "type": "view",
86
86
  "kind": "component",
87
87
  "props": {},
88
+ "blocks": [],
88
89
  "children": [
89
90
  {
90
91
  "elementId": "row_card",
@@ -118,6 +119,7 @@
118
119
  "type": "view",
119
120
  "kind": "component",
120
121
  "props": {},
122
+ "blocks": [],
121
123
  "children": [
122
124
  {
123
125
  "elementId": "lst_tasks_bos",
@@ -130,6 +132,7 @@
130
132
  "data": "Kayıt yok"
131
133
  }
132
134
  },
135
+ "blocks": [],
133
136
  "children": []
134
137
  }
135
138
  ]
@@ -197,7 +197,7 @@ Her şema düğümünde iki sözlük vardır; ikisi de ortak render katmanında
197
197
 
198
198
  | `appearance` | Değerler | Anlamı |
199
199
  |---|---|---|
200
- | `heightType` | `auto` · `fixed` · `fill` | `auto`: içerik kadar (önerilen — Kural 1d); `fixed`: `heightString` varsa o, yoksa `blocks.h × 10px`; `fill`: **kalanı doldur** — ebeveynin bıraktığı yer (ızgarada `height:100%`, flex sütununda `flex:1 1 auto`, flex satırında `align-self:stretch`). Tam ekran yerleşim kabuğu bununla kurulur, bkz. `platform_guide('layout')` |
200
+ | `heightType` | `auto` · `fixed` · `fill` | `auto`: içerik kadar (önerilen — Kural 1d); `fixed`: `heightString` varsa o, yoksa `blocks.h × 10px`; `fill`: **kalanı doldur** — ebeveynin bıraktığı yer (ızgarada `height:100%`, flex sütununda `flex:1 1 auto`, flex satırında `align-self:stretch`). Tam ekran yerleşim kabuğu bununla kurulur, bkz. `platform_guide('layout')`. **Mobilde (≤ 767 px) mobil bloğu olmayan her düğüme `height: auto !important` yazılır**: yüksekliği `height` olarak yazılan `fixed` düğüm (flex ya da CSS grid ebeveyni, stretch yokken) içeriğine göre çizilir; stretch'li satırdaki `min-height` ve 12 kolonlu eski ebeveynin `grid-row` yüksekliği bundan bağımsızdır. Yazılı yüksekliği mobilde de istiyorsan (`heightType: 'fixed'` iken) `appearance.className`'e ayrılmış **`ng-keep-height-mobile`** belirtecini yaz (`className: "hero ng-keep-height-mobile"`) |
201
201
  | `widthType` | `cover` · `contain` · `auto` · `fixed` · `fill` | İlk ikisi görsel/medya sığdırma. Kalanı **flex (ve eski cssgrid) kabın çocuğunun genişlik modu** (2026-09-01): `auto` içerik kadar, `fixed` `widthString`'den, `fill` kabın tüm genişliği (satırda `flex:1 1 100%`, sütunda `align-self:stretch`). **Yazılmazsa eski 12 kolon `blocks.size.w` payı geçerlidir** — öncelik: `fill` > `fixed` > `auto` > span |
202
202
  | `widthString` / `heightString` | CSS uzunluğu | Sabit ölçü; **yalnız ilgili `*Type: 'fixed'` iken okunur** (aksi PAGE-017 uyarısı). Kabul: çıplak sayı (px sayılır) · `px % vw vh vmin vmax rem em ch` birimli değer · `calc(...)`. Negatif/`pt`/`fr`/`auto` GEÇMEZ — platform kaydeder ama panel ve runtime sessizce düşürür (PAGE-018). Eski `widthPx` alanı 2026-09-01'de kaldırıldı |
203
203
  | `marginType` / `marginValue` | `normal` · `none` · `custom` + px | Dış boşluk: **yazılmazsa `normal` sayılır** = her kenara 4px (`spacing.ts` `CANVAS_NORMAL_MARGIN`); custom=`marginValue`. Bileşenleri dip dibe dizmek için `none` ŞART — `gap:'none'` olsa bile normal margin komşular arasında 8px bırakır (sıkı dizilim tarifi: `platform_guide('page')` Kural 1f-2) |
@@ -453,6 +453,8 @@ Companion'ı adından değil **ana entity'nin `localizationEntityId` alanından*
453
453
  UUIDv7) anahtar doğru kurulmuştur.
454
454
 
455
455
  Gateway bu iki kuralı ENT-006 (PK identity/tip uyumu) ve ENT-007 (enum şekli) olarak denetler.
456
+ `identity: true` olup `properties.generationType` 0/1 taşımayan anahtar ENT-006 **block**'tur: platform
457
+ create'te de update'te de 400 "Please select a valid generation type" döner (38 S-1).
456
458
 
457
459
  > **Çok dilli bir entity TEK adımda silinir.** `entity_delete` ana entity'ye uygulanır, companion
458
460
  > da onunla düşer. (Aynı gün daha erken bir ölçümdeki "önce companion'ı sil" adımı artık geçersiz;
@@ -486,6 +488,20 @@ boş bırakmak geçerli bir tercihtir** (ENT-002 warn'ı bunu hatırlatır, zoru
486
488
  > yolunda bu çeviriyi **platform** yapar; ham PUT atarken **siz yapmazsanız her güncelleme bağı
487
489
  > sessizce siler.**
488
490
 
491
+ Var olan entity'yi `entity_patch` ile uygulamaya bağlamanın çalışan şekli — yamalanan belge
492
+ `entity_get`'in okuma şeklidir, yol `/applications`, öğe `{ applicationId }`:
493
+
494
+ ```jsonc
495
+ // Bağ listesini yazar (var olan bağlar da listede olmalı — replace tam listedir)
496
+ { "op": "replace", "path": "/applications", "value": [{ "applicationId": "<application id>" }] }
497
+ // Tek bağ ekler
498
+ { "op": "add", "path": "/applications/-", "value": { "applicationId": "<application id>" } }
499
+ ```
500
+
501
+ `/applicationIds` yolu belgede yoktur; çıplak guid öğesi platformda 500 `JsonException`, `{ id }`
502
+ öğesi boş `applicationId` ile yanıltıcı "This record is in use. Related table: 'Applications'" verir
503
+ (37 S-1). Gateway üçünü de yukarıdaki şekle çevirir ve `gatewayNormalized` ile söyler.
504
+
489
505
  ## İlişkiler (doğru şekil — 57 alanla doğrulandı)
490
506
 
491
507
  ```jsonc
@@ -98,10 +98,10 @@ dalından devam eder.
98
98
  | Global | İçerik |
99
99
  |---|---|
100
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` |
101
+ | **Komponent adları** | Her komponent adıyla doğrudan değişkendir: `specialties_table.rows = data` prop'u yazar ve UI'ı günceller; `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
@@ -60,14 +60,15 @@ Studio'nun runtime rotasının üç şeridi vardır ve **ortamı URL belirler**:
60
60
  |---|---|
61
61
  | `production` | `/view/{proje}/{app}/{sayfa}` |
62
62
  | `development` | `/dev/{proje}/{app}/{sayfa}` |
63
- | dal | `/branch/{dal}/{proje}/{app}/{sayfa}` |
63
+ | dal | `/branch/{slug}/{proje}/{app}/{sayfa}` — slug, adın `/` yerine `-` taşıyan hâli (`feature/odeme` → `feature-odeme`) |
64
64
 
65
65
  Sayfanın **bütün** istekleri o dala gider: sayfa şeması da, akışların uç çağrıları da. Yani kare
66
66
  "yarı doğru" değildir — gördüğünüz ekran dalın gerçek ekranıdır.
67
67
 
68
- Reddedilen tek şey **dal adı olamayacak bir ad**: harfle başlamayan, küçük harf/rakam/alt çizgi
69
- dışında karakter taşıyan, 3-31 aralığının dışına çıkan ya da rezerve bir ad (`public`, `api`,
70
- `pg_*` …). Projedeki dalları `environment_list` verir.
68
+ Reddedilen tek şey **dal adı olamayacak bir ad**: harfle başlamayan, küçük harf/rakam/alt çizgi/tire
69
+ ve `/` ile ayrılmış parçalar dışında karakter taşıyan, 3-31 aralığının dışına çıkan ya da rezerve bir
70
+ ad (`public`, `api`, `pg_*` …). Projedeki dalları ve slug'larını `environment_list` verir; tenant
71
+ çağrılarına `environment` olarak adı da slug'ı da geçebilirsiniz.
71
72
 
72
73
  2026-09-09 öncesinde bu çağrı **reddediliyordu**: runtime rotası dal adı taşımıyordu, ortam
73
74
  modül içi bir store'dan geliyordu ve dal adıyla istenen kare development'ı gösterirdi. O günlerin
@@ -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
  ```
@@ -515,7 +515,7 @@ hem `joins` ile getirin hem `selectFields`'a koyun; ondan sonra `filters[0][fiel
515
515
  çalışır. m2m alanlar da desteklenir (`EXISTS` alt sorgusuna çevrilir).
516
516
 
517
517
  > **Expression filtresi istek parametresi TAŞIYAMAZ.** Microservice'te expression'lar
518
- > `workflowContext.JavascriptEngineInstance` ile eval edilir ve o bağlamda `input` global'i
518
+ > `workflowContext.CreateJavascriptEngine()` ile eval edilir ve o bağlamda `input` global'i
519
519
  > **yoktur** — `value: "input.doctor_id"` yazarsan uç `500 JavaScriptException: input is not
520
520
  > defined` verir (`input` workflow table step'inin sözlüğüdür, microservice'in değil).
521
521
  > Expression'ın okuyabildiği iki şey vardır: **sabit değerler** (`"'Aktif'"`, `new Date()`) ve
@@ -52,7 +52,7 @@ cevabı adımın ifadesinde gizlidir. Ayrıntı ve yazım ölçütü: `platform_
52
52
  3. **Değişkenler istek boyunca yaşar.** `set-user` / `variable` step'lerinin kurduğu
53
53
  değişkenler istek kapsamındaki `WorkflowContext`'e yazılır (`AddVariable`) ve her akış
54
54
  koşusunun JS motoru o anki değişkenlerden kurulur (`WorkflowRunner` →
55
- `JavascriptEngineInstance`). Hedef microservice'in **expression filtrelerinde/alanlarında**
55
+ `CreateJavascriptEngine()`). Hedef microservice'in **expression filtrelerinde/alanlarında**
56
56
  ve step ifadelerinde doğrudan okunur (`user.id`, `settings.auth_method`). Ayrıca microservice
57
57
  yolunda middleware'in response gövdesindeki `user` / `data` anahtarları da değişkene
58
58
  terfi eder (`ExecuteMicroserviceBehavior.cs:105` — sayfa yolunda bu terfi YOKTUR).
@@ -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.
@@ -70,6 +70,7 @@ metni yeni numarayla yeniden dener.
70
70
  | P-10 | `manual declarations_pending` | kullanıcı onayıyla `mobile_store_update { declarations }` (engellemez) |
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
+ | 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` |
73
74
 
74
75
  Satırın `message` ve `fixHint`'i backend'den, istek dilinde gelir.
75
76
 
@@ -98,9 +99,32 @@ sayfa · bileşen ya da akış adımı), `build` (production: `carried`, `reason
98
99
  taşımayan son mağaza derlemelerini (TestFlight / Play iç test) listeler; izin kullanılıyorsa yanıt
99
100
  `permissionWarnings` taşır — production kullanıyorsa yeni derleme şimdi, yalnız okunan ortam kullanıyorsa
100
101
  dağıtımdan sonra gerekir.
102
+ - **İzleme izni (`tracking`, iOS ATT, kök ADR 0011):** derleme taşıyorsa App Store Connect App Privacy'de "Device
103
+ ID — Used to Track You", Play Console Data safety'de "Advertising ID" beyanı insanın işidir; izin penceresinden önce
104
+ teşvik ya da kendi ön pencerenizi göstermeyin (App Store 5.1.2). Görünüm bunu `permissionWarnings`'te söyler
105
+ (`MOB-027`). Taşınmayan ikiliye `expo-tracking-transparency` hiç bağlanmaz. Reklam açıksa izin, App ID'si olan platformun derlemesinde taşınır (yalnız Android'de reklam varsa iOS ikilisi ATT taşımaz).
101
106
  - Tarama `setter` ile yazılan tetikleyici prop'u da sayar; `code` adımı, fonksiyon gövdesi, `webview` / `html`
102
107
  içeriği ve dış bileşen görünmez — onlar için kip `on`.
103
108
 
109
+ ## Reklamlar
110
+
111
+ Google Mobile Ads (kök ADR 0012) mağaza ayarıdır: `settings.ads = { enabled, iosAppId, androidAppId, testMode,
112
+ consentForm, appOpenUnit, units: [{ name, format, ios, android }] }`. App ID `ca-app-pub-…~…`, birim kimliği
113
+ `ca-app-pub-…/…`; `name` `^[a-z][A-Za-z0-9]{0,39}$` ve tekil (bileşen `adBanner.unit` ve adım `ad-show.value` bu adla
114
+ anar); `format` `banner` · `interstitial` · `rewarded` · `rewardedInterstitial` · `appOpen`; bir platformun kimliği boşsa
115
+ yerleşim o platformda yoktur. Biçim hatası `MOB-022` ile yazılmadan durur.
116
+
117
+ - SDK ikiliye yalnız `enabled` iken ve o platformun App ID'si varken bağlanır; o platformun derlemesine `tracking`'i de taşıtır (kip
118
+ `off` olamaz, 422).
119
+ - `testMode: true` (varsayılan) gerçek birim yerine Google test birimini gösterir — geliştirirken gerçek birime
120
+ tıklamak AdMob hesabını kapattırabilir. Yayından önce `false` (P-13 `manual`).
121
+ - `consentForm: true` (varsayılan) açılışta AdMob UMP rıza formunu gerekiyorsa gösterir; rıza mesajının kendisi AdMob
122
+ konsolunda kurulur (insan). Sıra: rıza → açılış izinleri (ATT) → SDK başlatma; rıza reklama izin vermezse uygulama
123
+ reklamsız sürer.
124
+ - `appOpenUnit` `appOpen` biçimli bir yerleşimin adı; soğuk açılışta bir kez gösterilir.
125
+ - `mobile_store_get` reklam açıkken `adsWarnings` döner (`MOB-026`): test kipi, App ID'si olan platformda birimi olmayan
126
+ yerleşim, konsol beyanları (App Privacy, Data safety, AdMob rıza mesajı — insanın işi).
127
+
104
128
  ## Hata kodları
105
129
 
106
130
  `mobile_build_status` → `error.code`.
@@ -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`, `grouped-list`, `floating-container`, `skeleton`, `photo-viewer`) bilgi yalnız
40
+ `audio`, `lottie`, `webview`, `map`, `adBanner`, `grouped-list`, `floating-container`, `skeleton`, `photo-viewer`) bilgi yalnız
41
41
  `mobileKnowledge`'tadır. Olay yükleri
42
42
  (`events[].payload`) önce mobile özgü sözleşmeden okunur (`app-bar.clickAction`, `repeater.swipe`,
43
43
  `choice-chips.change`…), yoksa web'inkinden.
@@ -91,6 +91,11 @@ mobil bileşenlere geçen notlar). Web'de karşılığı olmayan bileşende (`ap
91
91
  OpenStreetMap): görünüm `latitude`/`longitude`/`zoom` sayılarıdır ve akış yazınca harita oraya gider;
92
92
  kullanıcı haritayı hareket ettirince `regionChange { latitude, longitude, zoom }` düğüme geri yazılır,
93
93
  işaretçiye dokunmak `markerClick { markerId }` yayar.
94
+ `adBanner` mobile özgüdür (Google Mobile Ads banner'ı, kök ADR 0012): `unit` mağaza ayarındaki reklam
95
+ yerleşiminin **adıdır** (birim kimliği değil), `size` `banner` 320×50 · `largeBanner` · `mediumRectangle` 300×250 ·
96
+ `fullBanner` · `leaderboard` · `adaptive` · `inlineAdaptive`. Reklamı yalnız mağaza sürümü çizer; kanvas, önizleme ve
97
+ oynatıcı boyutlu yer tutucu çizer ve çalışma kipinde `fail { code: 'unavailable' }` yayar. Mağaza sürümünde reklam
98
+ hazır değilken (rıza, SDK) yer kaplamaz; yerleşim yoksa ya da yüklenemezse `fail` yayar ve yer kaplamaz.
94
99
  Dördüncü adım: `datepicker`, `timepicker` ve `datetimepicker` web ikizleridir — kutuya dokununca cihazda
95
100
  işletim sisteminin seçicisi (iOS alttan açılan sayfa, seçim Tamam'la yazılır; Android iletişim kutusu),
96
101
  ö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
@@ -174,12 +179,12 @@ istekle gider.
174
179
  Gateway alan tipini şemadan bilemediği için uyarı metinseldir: `.value` ile biten en az üç parçalı
175
180
  yol (`urlInput.value` gibi bileşen değeri ve `fileInput1.value.value` uyarı almaz).
176
181
 
177
- Workflow'un dosya yükleme adımı yalnız `{ id }` döndürür (URL yok); görsel adresi kaydı okuyan uçtan
178
- gelir. `page_screenshot` ile listede görseli doğrulayın.
182
+ Workflow'un `file-upload` adımı da aynı nesneyi döndürür, adres `<adım>.data.absolute_path`
183
+ (`platform_guide('workflow')`). `page_screenshot` ile listede görseli doğrulayın.
179
184
 
180
185
  ## Liste ve boş durumu
181
186
 
182
- 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
187
+ Mobil `repeater`'ın çocukları TAM İKİ `view`'dır (web'de tek şablon) — satır şablonu ve boş görünüm (ADR
183
188
  docs/adr/0005; başka şekil 422 `fixed-views`). Satır yokken boş görünüm bir kez, tam genişlikte
184
189
  çizilir; boş ekranı oraya tasarla. `emptyTitle`/`emptyDescription`/`emptyIcon` prop'ları ve kardeş
185
190
  `emptystate` yolu 2026-09-30'da kaldırıldı. Mobil `view` çocuklarını sütunda dizer. Tam gövde:
@@ -199,15 +204,15 @@ bölümleri bileşen render anında çıkarır.
199
204
  3. **Çocuklar konumsaldır:** çocuk 0 bölüm başlığı, çocuk 1 öğe şablonu; fazlası çizilmez.
200
205
 
201
206
  ```json
202
- { "elementId": "tasks", "name": "tasks", "type": "grouped-list",
207
+ { "elementId": "tasks", "name": "tasks", "type": "grouped-list", "blocks": [],
203
208
  "props": { "rows": { "type": "code", "ref": "{{ q_tasks.data }}" },
204
- "groupBy": { "type": "static", "data": "dueDate" },
205
- "itemPressable": { "type": "static", "data": true } },
209
+ "groupBy": { "type": "static", "data": "dueDate" },
210
+ "itemPressable": { "type": "static", "data": true } },
206
211
  "children": [
207
- { "elementId": "day_title", "name": "day_title", "type": "text",
212
+ { "elementId": "day_title", "name": "day_title", "type": "text", "blocks": [],
208
213
  "props": { "content": { "type": "code", "ref": "{{ Section.title }} · {{ Section.count }}" } } },
209
- { "elementId": "task_row", "name": "task_row", "type": "container", "props": {},
210
- "children": [ { "elementId": "task_title", "name": "task_title", "type": "text",
214
+ { "elementId": "task_row", "name": "task_row", "type": "container", "props": {}, "blocks": [],
215
+ "children": [ { "elementId": "task_title", "name": "task_title", "type": "text", "blocks": [],
211
216
  "props": { "content": { "type": "code", "ref": "{{ item.title }}" } } } ] } ] }
212
217
  ```
213
218
 
@@ -215,8 +220,8 @@ bölümleri bileşen render anında çıkarır.
215
220
  `Section` boş nesnedir (`Row` gibi). Öğe repeater satırıyla aynıdır: `Row`, `RowIndex`,
216
221
  `{{ item.<alan> }}`; akışta tıklanan satır `tasks.selectedItem`. Şablon çocukları setter hedefi
217
222
  değildir — satır dışına (sayfadaki bir metne, modala) yazın.
218
- 4. **Olaylar.** `itemPress { index, row }` (yalnız `itemPressable`; öğe şablonu `list-item`/`button`
219
- ise dokunuş onun `click`'ine gider), `swipe { key, index, row }` (`swipeActions`),
223
+ 4. **Olaylar.** `itemPress { index, row }` (yalnız `itemPressable`; öğe şablonu `button` ya da `click`'i
224
+ bağlı `list-item` ise dokunuş onun `click`'ine gider — bağsız `list-item` basılamaz), `swipe { key, index, row }` (`swipeActions`),
220
225
  `change { selectedIndex }`, `sectionPress` (yük `Section`). `index` `rows` içindeki DÜZ indekstir.
221
226
  5. **Yapışkan gün başlığı.** `stickySectionHeaders: true` + `appearance.heightType: 'fixed'` +
222
227
  `heightString`. Sayfanın kendisi kaydırıcıdır; `auto` ya da `fill` liste içerik kadar uzar ve başlık
@@ -236,10 +241,10 @@ düğümü sayfa kök kabının SONUNA koyun (akıştaki yeri boş kalır).
236
241
  sayılır), `respectSafeArea` (varsayılan açık). Alt eylem çubuğu:
237
242
 
238
243
  ```json
239
- { "elementId": "action_bar", "name": "action_bar", "type": "floating-container",
244
+ { "elementId": "action_bar", "name": "action_bar", "type": "floating-container", "blocks": [],
240
245
  "props": { "anchor": { "type": "static", "data": "bottom-center" },
241
- "fullWidth": { "type": "static", "data": true } },
242
- "children": [ { "elementId": "bar", "name": "bar", "type": "container",
246
+ "fullWidth": { "type": "static", "data": true } },
247
+ "children": [ { "elementId": "bar", "name": "bar", "type": "container", "blocks": [],
243
248
  "props": { "direction": { "type": "static", "data": "row" } }, "children": [] } ] }
244
249
  ```
245
250
 
@@ -406,8 +411,8 @@ Motorun kendi adımları (`setter`, `condition`, `microservice`, `code`, `functi
406
411
  |---|---|
407
412
  | `redirect` | yığına iter (göreli yol) ya da dışarıda açar (tam adres) |
408
413
  | `navigate` | eski mobil şemaların adı — `redirect`'in takma adıdır; gateway `redirect`'e çevirir |
409
- | `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 }`) |
410
- | `confirm` | `yes` / `no` dalları |
414
+ | `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 }`) |
415
+ | `confirm` | `yes` / `no` dalları; `message`/`title`/`confirmText`/`cancelText` metin şablonudur (`{{ }}`, çeviri `t('anahtar')`) |
411
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) |
412
417
  | `storage` | `args.name` `state.page.<key>` / `state.app.<key>` ise o state'i yazar (§Kalıcı state), değilse cihaz deposuna |
413
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 |
@@ -416,10 +421,12 @@ Motorun kendi adımları (`setter`, `condition`, `microservice`, `code`, `functi
416
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` |
417
422
 
418
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` |
419
- | `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 |
420
- | `notification-cancel` | `args.name` kimlikli planlı bildirimi siler, `'*'` hepsini; hep `always` |
421
- | `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. Red hata değildir, hep `always`. Türler: `mobile_store_get { detail: ['permissions'] }` (`support: supported` satırlarının `key`'i) |
424
+ | `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 |
425
+ | `notification-cancel` | `args.name` kimlikli planlı bildirimi siler (planlamadaki gibi metin şablonu: `'ilac-{{ item.id }}-0800'`), `'*'` 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) |
422
427
  | `open-settings` | uygulamanın sistem ayar sayfasını açar; `Steps.<adım>.data.opened`. Argüman almaz |
428
+ | `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 |
429
+ | `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 |
423
430
 
424
431
  Bildirim adımları paletten yalnız mobil uygulamada sunulur. Tarif — S04 "her gün hatırlat": önce
425
432
  `notification-permission` (S03 düğmesi), sonra `timepicker`'ın değeriyle `notification-schedule`
@@ -434,13 +441,26 @@ kamera/galeri kaynağı izni kendileri de ister — `permission` adımı *ne zam
434
441
  okumak içindir. `unavailable` bir hata değil, derlemenin o izni taşımadığının haberidir
435
442
  (`platform_guide('mobile-publish')` §İzinler).
436
443
 
444
+ **Reklam tarifi** (kök ADR 0012): (1) `mobile_store_update { settings: { ads: { enabled: true, iosAppId, androidAppId,
445
+ testMode: true, units: [{ name, format, ios, android }] } } }` — reklam, App ID'si olan platformun derlemesine `tracking`'i de sokar (ATT metni
446
+ gider, kip `off` olamaz); (2) banner için sayfaya `adBanner` `unit: '<banner yerleşimi>'`; (3) geçiş/ödüllü reklam için
447
+ düğme akışına `ad-show` `value: '<yerleşim>'`, `error` dalında reklamsız devam; (4) ödül `condition`:
448
+ `Steps.<adım>.data?.reward` doluysa ödülü ver. Açılış reklamı adım değil ayardır (`ads.appOpenUnit`). Sıra cihazda:
449
+ rıza formu → açılış izinleri (ATT) → SDK; iOS'ta izleme izni yoksa reklam kişiselleştirilmeden istenir. Yerleşim adının
450
+ ayarda tanımlı olduğu sayfa/akış yazımında denetlenemez — `mobile_store_get` `settings.ads.units` ve `adsWarnings`
451
+ (`MOB-026`); yayın ayrıntısı `platform_guide('mobile-publish')` §Reklamlar.
452
+
437
453
  `style`, `accuracy`, `reverseGeocode` gibi anahtarlar sözleşmede yoktur ve platform onları sessizce düşürür
438
454
  (`FLOW-029`). "Haritada aç" için adım yoktur: `map`'in `press` olayı düğüme `mapsUrl` yazar, akış
439
455
  `redirect` `isRaw: true`, `path: '<harita>.mapsUrl'` ile açar. Tarif — L05 "mevcut konumumu kullan":
440
456
  düğmenin `click`'i → `location` (`always` → setter `il.value = Steps.konum.data.address?.region`, `error` →
441
457
  setter hata metni). Uzak push (sunucudan gelen bildirim) ayrı bölümde: §Uzak push.
442
458
 
443
- Sayfa ve layout `mount` akışları ilk çizimden sonra, `destroy` akışları sayfa kalkarken koşar.
459
+ Sayfa ve layout `mount` akışları ilk çizimden sonra, `destroy` akışları sayfa kalkarken koşar. Yığındaki
460
+ ve sekmedeki ekran bağlı kalır: geri dönüşte `mount` KOŞMAZ. Listeyi yeniden okumak için sayfanın
461
+ **`focus`** olayı vardır (layout'ta yok): ilk görünüşte `mount`'tan sonra ve her geri dönüşte / sekme
462
+ değişiminde koşar. Bağ: `page_update` `events: [{ name: 'focus', value: <flow id> }]`. Form doldurma
463
+ `mount`'ta, liste okuması `focus`'ta — aynı okuma ikisine bağlanırsa ilk açılışta çift istek gider.
444
464
  `Functions.<ad>()` uygulama ve sayfa fonksiyonlarını görür (`platform_guide('functions')`).
445
465
 
446
466
 
@@ -473,7 +493,8 @@ Sunucudan gönderilen bildirim yerel `notification-schedule`'dan ayrıdır. Kana
473
493
  ## Kalıcı state
474
494
 
475
495
  State varsayılan olarak **bellektedir**: sayfa her açıldığında `variable_create`'te yazılan `value`'dan
476
- başlar, akışın yazdığı değer ekran kapanınca kaybolur. Uygulama kapanıp açıldığında kaldığı yerden
496
+ başlar, akışın yazdığı değer ekran kapanınca kaybolur — `state.app` dahil: sayfalar arası
497
+ taşımak da `storage` ister (38 S-5). Uygulama kapanıp açıldığında kaldığı yerden
477
498
  sürmesi gereken değer için değişkene saklama türü verin:
478
499
 
479
500
  | `storage` | Depo | Ne için |
@@ -511,7 +532,10 @@ variable_create({ applicationId, pageId, variables: [
511
532
  780x1688 px) çeker; `'tablet'` 820x1180 @2 ya da `{ width, height, scale? }`. `viewport` verilirse
512
533
  cihaz yok sayılır (`deviceNote`). Yanıt `applicationType: 'mobile'` ve `platformNote` taşır: kare
513
534
  react-native-web çizimidir, cihazdakiyle yapısal olarak (düğüm, sıra, ölçü, renk) aynıdır.
514
- - Eylemler aynı sözlük: `click` basılabilir öğeyi (`role="button"`) bulur; `submit` runtime
535
+ - Eylemler aynı sözlük: `click` önce düğümün kendisine (basılabilirse: `role="button"`/`"switch"`/
536
+ `"combobox"`), sonra `<elementId>__press` yüzeyine, en son içteki ilk düğmeye basar — anahtar ve
537
+ onay kutusu kök kimlikle çevrilir, çoklu seçimi `<elementId>__trigger` açar (çipin "kaldır"ına
538
+ basmaz); `set` alana odaklanır, yazar ve odaktan çıkar (`changed` koşar); `submit` runtime
515
539
  köprüsüyle gider (`submitVia: 'bridge'`); sekme öğesi `__tabs__item-<sayfa slug'ı>`, çubuk `__tabs`
516
540
  ö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
517
541
  (liste katmanda açılır, `optionsSeen` kaç seçenek görüldüğünü söyler); `tab` bir `tabs`
@@ -544,6 +568,7 @@ yaptıkları. Derlenen uygulama
544
568
  - Uygulama içi tema seçimi (Sistem / Açık / Koyu) bir ayardır, bileşen değil: akış `storage` adımıyla
545
569
  `zeyrek:theme-mode` anahtarına `system` · `light` · `dark` yazar (ör. segmentli `choice-chips`'in `change`'i,
546
570
  `value: '<çipler>.value'`); barındırıcı anahtarı dinler, tema anında döner ve cihazda kalıcıdır — durum çubuğu da
547
- izler. Web'in `theme-toggle`'ı mobilde yoktur. Seçili düğme saklanan seçimi `LocalStorage`'dan okur:
548
- `{{ LocalStorage['zeyrek:theme-mode'] || 'system' }}` (ARC-147).
571
+ izler. Seçim yazılmadıysa (ilk açılış) tema **açıktır**; cihazın şeması yalnız `system` açıkça seçilince izlenir.
572
+ Web'in `theme-toggle`'ı mobilde yoktur. Seçili düğme saklanan seçimi `LocalStorage`'dan okur:
573
+ `{{ LocalStorage['zeyrek:theme-mode'] || 'light' }}` (ARC-147).
549
574
  - Dar ekran kırılımı (`hideOn: 'mobile'`) mobil uygulamada anlamsızdır: ekran zaten tek boydur.