@appaflytech/zeyrek-ai-mcp 0.0.45 → 0.0.46

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.
@@ -319,7 +319,7 @@ export const mobileComponentKnowledge = {
319
319
  '`imageFit` RN `resizeMode`a çevrilir: cover→cover, contain→contain, fill→stretch, none→center.',
320
320
  '`surfaceStyle` (mobile özgü) kartın ÇERÇEVESİDİR: köşe, kenar, zemin, gölge sınıf tabanını ezer (`radius: "28px 28px 0 0"` üstü yuvarlak kart). Kabuk grubu ikinci kez çizmez.',
321
321
  "`image` `image` bileşeniyle aynı çözümden geçer: kayıttaki FILE alanını nesne olarak verin (`{{ item.foto }}`) ya da `.absolute_path` yazın; `.value` dosya kimliğidir (GUID), görsel çizilmez (MOB-011).",
322
- 'Kartın TAMAMI dokunulabilir: gövdeye dokunmak `click` yayar. Kartın düğmesi (`buttonText`) web `Card.svelte` sırasıyla önce kendi `buttonClick`ini, ardından kartın `click`ini yayar (2026-09-27, f-02 H10) — düğmeye ayrı akış bağlamak için `buttonClick`i kullanın; `click` akışı iki dokunuşta da koşar. Liste satırına dokunmak repeater şablonundaki kartın `click`idir. Kart çocuk ALMAZ — satırın alanlarını `title`/`description` kod bağıyla (`{ type: \'code\', ref: \'{{ item.alan }}\' }`) verin.'
322
+ 'Kartın gövdesi yalnız `click`e akış BAĞLIYKEN dokunulabilirdir ve dokunuş `click` yayar; bağlı değilse gövde düz kutudur, dokunuş üstteki basılabilir satıra (`itemPressable` repeater → `itemPress`) geçer (2026-10-20, ARC-338: bağsız kart da düğmeydi, satırın `itemPress`i hiç koşmuyordu). Kartın düğmesi (`buttonText`) web `Card.svelte` sırasıyla önce kendi `buttonClick`ini, ardından kartın `click`ini yayar (2026-09-27, f-02 H10) — düğmeye ayrı akış bağlamak için `buttonClick`i kullanın; `click` akışı iki dokunuşta da koşar. Liste satırına dokunmak repeater\'ın `itemPress` olayıdır (`itemPressable: true`); satırdaki karta ayrıca `click` bağlanırsa dokunuşu kart alır ve satırın `itemPress`i koşmaz. Kart çocuk ALMAZ — satırın alanlarını `title`/`description` kod bağıyla (`{ type: \'code\', ref: \'{{ item.alan }}\' }`) verin.'
323
323
  ]
324
324
  },
325
325
  emptystate: {
@@ -378,10 +378,18 @@ export function profileFindings(profile, input) {
378
378
  const badPresentation = new Map();
379
379
  const badTransition = new Map();
380
380
  const unusedTransition = new Map();
381
+ const statusNetwork = [];
381
382
  walkSteps(input.steps, step => {
382
383
  const type = String(step.type ?? '');
383
384
  if (profile.steps.infoOnly.has(type))
384
385
  hit(infoSteps, type, String(step.stepId ?? step.name ?? '?'));
386
+ // MOB-033 (bilgi): ağ hatasını 503 ile ayıran ifade. Akış hangi adımın argümanında olursa olsun ifadeyi metin olarak
387
+ // taşır (koşul, alert şablonu, setter), bu yüzden argümanların tamamı taranır. Kural metinseldir: uygulamanın veri
388
+ // önbelleği açık mı, lint bilmez — mesaj koşulu söyler. Adım `error.network`'ü de okuyorsa (`== 503 && !…network`,
389
+ // `network || == 503`) sunucu arızasını ağdan ayıran doğru desendir: not düşmez.
390
+ const argsText = JSON.stringify(step.args ?? null);
391
+ if (STATUS_503.test(argsText) && !READS_NETWORK.test(argsText))
392
+ statusNetwork.push(String(step.stepId ?? step.name ?? '?'));
385
393
  // MOB-008: sunum biçimi yalnız `redirect`te anlamlı ve kümesi kapalı.
386
394
  // Alan YOKSA bulgu yok — varsayılan `push` ve yazımların çoğu onu yazmaz.
387
395
  if (type !== 'redirect')
@@ -435,6 +443,15 @@ export function profileFindings(profile, input) {
435
443
  guide: "platform_guide('page') §Kural 1g-2"
436
444
  });
437
445
  }
446
+ if (statusNetwork.length > 0) {
447
+ findings.push({
448
+ code: MOBILE_RULES['MOB-033'].id,
449
+ severity: 'info',
450
+ target: statusNetwork.join(', '),
451
+ message: "akış ağ hatasını error.status 503 karşılaştırmasıyla ayırıyor — 503'ü sunucu da döner (bakım, aşırı yük). Uygulamanın veri önbelleği açıksa (properties.mobile.offline.data: true) yanıt alınamayan istekte Steps.<ad>.error.network true olur ve status yine 503 kalır: 'bağlantı yok' demek için error.network'e bakın. Önbellek kapalıyken network alanı yazılmaz, 503 karşılaştırması tek yoldur.",
452
+ guide: "platform_guide('page') §Kural 1g-4"
453
+ });
454
+ }
438
455
  for (const [type, stepIds] of infoSteps) {
439
456
  findings.push({
440
457
  code: MOBILE_RULES['MOB-005'].id,
@@ -447,6 +464,12 @@ export function profileFindings(profile, input) {
447
464
  }
448
465
  return findings;
449
466
  }
467
+ /**
468
+ * `error.status == 503` ya da `503 === … error.status` (`!=`, `?.`, tırnaklı `'503'` dahil — argümanlar JSON metnidir,
469
+ * çift tırnak `\"` olur); `5030` gibi uzun sayı eşleşmez.
470
+ */
471
+ const STATUS_503 = /error\??\.status\s*[!=]==?\s*\\?['"]?503(?!\d)|(?<![\w.])503\\?['"]?\s*[!=]==?\s*[\w$.?]*error\??\.status/;
472
+ const READS_NETWORK = /error\??\.network/;
450
473
  function walkSteps(value, visit) {
451
474
  if (!Array.isArray(value))
452
475
  return;
@@ -0,0 +1,45 @@
1
+ export const MOBILE_OFFLINE_MAX_AGE_DAYS = [1, 7, 30];
2
+ export const MOBILE_OFFLINE_DEFAULTS = { screens: true, data: false, maxAgeDays: 7, banner: true };
3
+ const FIELDS = Object.keys(MOBILE_OFFLINE_DEFAULTS);
4
+ /** `properties.mobile.offline`'ın HAM değeri; alan yoksa `undefined`. */
5
+ export function rawMobileOffline(properties) {
6
+ const mobile = properties?.mobile;
7
+ return mobile && typeof mobile === 'object' ? mobile.offline : undefined;
8
+ }
9
+ /** Cihazın okuduğu ayar: küme dışı ya da yanlış tipli değer varsayılana düşer. */
10
+ export function readMobileOffline(properties) {
11
+ const raw = rawMobileOffline(properties);
12
+ const offline = raw && typeof raw === 'object' ? raw : {};
13
+ return {
14
+ screens: typeof offline.screens === 'boolean' ? offline.screens : MOBILE_OFFLINE_DEFAULTS.screens,
15
+ data: typeof offline.data === 'boolean' ? offline.data : MOBILE_OFFLINE_DEFAULTS.data,
16
+ maxAgeDays: MOBILE_OFFLINE_MAX_AGE_DAYS.includes(offline.maxAgeDays) ? offline.maxAgeDays : MOBILE_OFFLINE_DEFAULTS.maxAgeDays,
17
+ banner: typeof offline.banner === 'boolean' ? offline.banner : MOBILE_OFFLINE_DEFAULTS.banner
18
+ };
19
+ }
20
+ /**
21
+ * MOB-033'in ayar ayağı. Backend `properties`'i doğrulamaz (jsonb serbest) ve cihazın okuyucusu okuyamadığı değeri
22
+ * SESSİZCE varsayılana düşürür — uyarının söylendiği tek yer burasıdır. Yazım yine platforma gider.
23
+ */
24
+ export function mobileOfflineProblems(properties) {
25
+ const raw = rawMobileOffline(properties);
26
+ if (raw === undefined || raw === null)
27
+ return [];
28
+ if (typeof raw !== 'object' || Array.isArray(raw)) {
29
+ return [`properties.mobile.offline nesne olmalı (gelen: ${JSON.stringify(raw)}) — cihaz okumaz, varsayılanlar geçerli (screens: true, data: false, maxAgeDays: 7, banner: true)`];
30
+ }
31
+ const offline = raw;
32
+ const problems = [];
33
+ for (const field of ['screens', 'data', 'banner']) {
34
+ if (offline[field] !== undefined && typeof offline[field] !== 'boolean') {
35
+ problems.push(`properties.mobile.offline.${field} boolean olmalı (gelen: ${JSON.stringify(offline[field])}) — yok sayılır, varsayılan (${MOBILE_OFFLINE_DEFAULTS[field]}) geçerli`);
36
+ }
37
+ }
38
+ if (offline.maxAgeDays !== undefined && !MOBILE_OFFLINE_MAX_AGE_DAYS.includes(offline.maxAgeDays)) {
39
+ problems.push(`properties.mobile.offline.maxAgeDays ${JSON.stringify(offline.maxAgeDays)} kümede değil (${MOBILE_OFFLINE_MAX_AGE_DAYS.join(' | ')}) — sayı olarak yazılır; yok sayılır, ${MOBILE_OFFLINE_DEFAULTS.maxAgeDays} gün geçerli`);
40
+ }
41
+ for (const key of Object.keys(offline).filter(key => !FIELDS.includes(key))) {
42
+ problems.push(`properties.mobile.offline içinde tanınmayan anahtar '${key}' — cihaz okumaz (${FIELDS.join(' | ')})`);
43
+ }
44
+ return problems;
45
+ }
@@ -132,6 +132,12 @@ export const MOBILE_RULES = {
132
132
  severity: 'info',
133
133
  sourceRef: 'zeyrek-frontend/packages/ng-apps-ui-native/src/motion/hero-tag.ts (kural; ayna heroTag.ts); zeyrek-frontend/docs/adr/0022-hero-yolu.md; simülatör ölçümü 2026-10-10',
134
134
  summary: "Aynı sayfada aynı statik heroTag'i birden çok düğüm taşıyor — sayfa yakınlaşmanın hedefi olduğunda ilk çizilen hizalanır; kaynak sayfada sorun değildir"
135
+ },
136
+ 'MOB-033': {
137
+ id: 'MOB-033',
138
+ severity: 'warn',
139
+ sourceRef: 'docs/adr/0023-mobil-cevrimdisi-yerel-kopya-ve-uygulama-paketi.md K13, K8; zeyrek-frontend/packages/ng-apps-ui-native/src/shell/mobile-offline.ts (okuyucu; ayna mobileOffline.ts); ng-apps-flow tasks/microservice/microservice.ts (error.network yalnız veri önbelleği açıkken)',
140
+ summary: "properties.mobile.offline okunamıyor (nesne değil, boolean olmayan screens/data/banner, 1 | 7 | 30 dışı maxAgeDays, tanınmayan anahtar) — cihaz varsayılana düşer; akış ağ hatasını error.status 503 ile ayırıyor (bilgi: error.network)"
135
141
  }
136
142
  };
137
143
  /**
@@ -155,11 +155,13 @@ export function readsBeforeGuard(source, root) {
155
155
  * KENDİSİ tanır (ADR 0004, ZM-07/g-02): bağlıysa hata anında `always` hiç koşmaz, sürer
156
156
  * (microservice.ts:182 `hasBranch`) — eski motor tanımadığı dalı hiç aramadığı için bu ekleme
157
157
  * geriye dönük zararsızdır. `validation` tüm kurallar geçince 'success', biri düşünce 'error' dalına,
158
- * ardından her durumda 'always'e gider (validation.ts; kök ADR 0014, g-18).
158
+ * ardından her durumda 'always'e gider (validation.ts; kök ADR 0014, g-18). microservice'in 'queued' dalı mobilde,
159
+ * `offlineWritable` serviste ağ hatası kuyruğa alınınca koşar (kök ADR 0022, plans/mobil-cevrimdisi-calisma g-02);
160
+ * mobil/bildirim koşulunu tanım taşımadığı için akış aracı FLOW-043 ile platformdan okur.
159
161
  */
160
- const flowBranchNames = {
162
+ export const flowBranchNames = {
161
163
  start: ['always'],
162
- microservice: ['always', 'success', 'error'],
164
+ microservice: ['always', 'success', 'error', 'queued'],
163
165
  validation: ['always', 'success', 'error'],
164
166
  confirm: ['yes', 'no', 'always'],
165
167
  function: ['always'],
@@ -654,6 +656,26 @@ export const policyRules = [
654
656
  }];
655
657
  }
656
658
  },
659
+ {
660
+ id: 'MS-025',
661
+ module: 'microservice',
662
+ appliesTo: ['create', 'update'],
663
+ sourceRef: "Kök ADR 0022 (b-03, g-02): 'offlineWritable' kuyruktan gelen yazmayı kabul eder; okuyan uç hiç kuyruğa girmez — akış okumayı veri önbelleğinden alır (faz 04). Okuyan: tablo 'get'/'count' (MicroserviceTableOperation'da 'list' yoktur) ve görünüm kaynağı (ViewMicroserviceExecutor yalnız arar). Bildirim orada anlamsızdır ve okunan ucu 'yazan' gibi gösterir",
664
+ check: definition => {
665
+ const parameters = (definition.sourceParameters ?? {});
666
+ const source = String(definition.source ?? '').toLowerCase();
667
+ const operation = String(parameters.operation ?? '').toLowerCase();
668
+ const reads = source === 'view' || (source === 'table' && ['get', 'count'].includes(operation));
669
+ if (definition.offlineWritable !== true || !reads)
670
+ return [];
671
+ return [{
672
+ ruleId: 'MS-025',
673
+ severity: 'warn',
674
+ message: `offlineWritable bir okuma ucuna verilmiş (${source === 'view' ? "kaynak: 'view'" : `operation: '${operation}'`}): kuyruk yalnız yazmaları tutar, okuma çevrimdışıyken veri önbelleğinden gelir (uygulamanın 'mobile.offline.data' ayarı). Bildirimi kaldırın; yazan uçta (create/update/upsert/delete ya da yazan iş akışı) kalsın.`,
675
+ guide: 'ngapps://guides/page'
676
+ }];
677
+ }
678
+ },
657
679
  {
658
680
  id: 'QRY-006',
659
681
  module: 'query',
@@ -0,0 +1,357 @@
1
+ {
2
+ "_meta": {
3
+ "synthetic": true,
4
+ "note": "Mobil uygulamada çevrimdışı okunan liste (kök ADR 0023): uygulamanın veri önbelleği açıktır; 'siparisler' mount'ta GET servisini çağırır, satırları repeater'a ve 'son güncelleme' satırını adımın önbellek işaretinden (Steps.<ad>.cache.at) yazar. Ağ varken satır gizli kalır; ağ yokken son başarılı yanıt saklandığı anla birlikte çizilir. Kayıt da yoksa hata dalı 'bağlantı yok'u Steps.<ad>.error.network'ten ayırır. Sayfa mizanpaj sözleşmesine uyar, akış mobil profilden bulgusuz geçer (test/mobile-rules.test.ts, test/mobile-layout-lint.test.ts).",
5
+ "pairsWith": "mobile-list-detail-form (aynı liste deseninin satıra dokunma ve form ayağı), mobile-app-shell-stack ya da mobile-app-shell-tab (kabuk)",
6
+ "sourceRef": "platform_guide('page') §Kural 1g-4; MOB-033; kök docs/adr/0023-mobil-cevrimdisi-yerel-kopya-ve-uygulama-paketi.md K2, K8, K13"
7
+ },
8
+ "_rules": {
9
+ "setting": "Veri önbelleği uygulamanın ayarıdır: application_update properties.mobile.offline = { data: true } (isteğe bağlı maxAgeDays: 1 | 7 | 30, varsayılan 7; screens varsayılan açık, yazılmaz). properties gövdesi REPLACE'tir: application_get'teki properties'in TAMAMI (mobile.tabs, mobile.transition ve öteki anahtarlar) aynen gönderilir, yalnız mobile.offline eklenir — eksik gönderilen anahtar silinir. Okunamayan değer MOB-033 uyarısıdır; cihaz onu varsayılana düşürür.",
10
+ "whatIsStored": "Saklanan: isRaw: false yazılmış GET servis adımlarının yanıtı (isRaw yazılmamış ya da true olan, dosya/akış sonucu dönen adım saklanmaz) ve connectData okumaları (list, describe, lookup). Yazma servisleri (POST/PUT/DELETE) ve connectData yazmaları saklanmaz, ağ yokken hata verir. Kayıtlar kullanıcıya ve ortama bağlıdır; çıkışta ve kullanıcı değişince silinir.",
11
+ "cacheMark": "Önbellekten yanıtlanan adım BAŞARI yolundan sürer ve storage'ında cache: { at } taşır (at: saklandığı an, ms). Ağdan gelen yanıtta cache alanı YOKTUR — satırın gizli kalmasını buradan alın: isHidden = !Steps.load_orders.cache.",
12
+ "networkError": "Yanıt hiç alınamadıysa (bağlantı yok; saklanan okumalarda 8 sn süre sınırı da) ve kayıt yoksa adım hata dalına gider: Steps.<ad>.error.network true, status 503. 503'ü sunucu da döner — 'bağlantı yok' için network'e bakın (MOB-033 bilgi notu; status == 503 && !network sunucu arızasını ayıran doğru desendir, not almaz). network alanı yalnız veri önbelleği açık uygulamada yazılır. Saklanmayan isteklerde süre sınırı yoktur: yanıt vermeyen sunucuda adım bekler.",
13
+ "stale": "Önbellek son başarılı yanıtı aynen verir: her çağrıda değişen bir GET (sayaç, zaman, rastgele) ağ yokken eski değerini döner. Parametreler ve veri dili isteğin parçasıdır; başka parametreli çağrı başka kayıttır.",
14
+ "detailFromSteps": "Satırlar ve güncelleme satırı SETTER adımıyla yazılır: `{{ Steps.… }}` kod bağı ilk çizimde okunur, Steps'e yazmak düğümü yeniden okutmaz (mobile-list-detail-form ile aynı desen).",
15
+ "preview": "Önizleme (/dev, Studio, page_screenshot) önbellek KULLANMAZ — ayar açık olsa da her okuma ağdan gider ve cache alanı hiç yazılmaz; güncelleme satırı önizlemede gizli kalır. page_screenshot ağı kesemez: çevrimdışı davranış yalnız cihazda (uçak modu) görülür.",
16
+ "verify": "page_runtime_get ile sayfa paketi ve akış okunur; page_screenshot({ applicationSlug: '<uygulama>', pageSlug: 'siparisler' }) satırları çizer, txt_guncelleme görünmez."
17
+ },
18
+ "pages": [
19
+ {
20
+ "applicationUpdateArgs": {
21
+ "properties": {
22
+ "...": "<application_get'teki properties'in öteki anahtarları — gövde REPLACE, hepsi aynen gönderilir>",
23
+ "mobile": {
24
+ "...": "<mevcut properties.mobile anahtarları (tabs, transition) aynen>",
25
+ "offline": {
26
+ "data": true
27
+ }
28
+ }
29
+ }
30
+ },
31
+ "pageCreateArgs": {
32
+ "name": "Siparişler",
33
+ "slug": "siparisler"
34
+ },
35
+ "schemaPatchOps": [
36
+ {
37
+ "op": "add_component",
38
+ "parent": "page_container",
39
+ "component": {
40
+ "elementId": "txt_baslik",
41
+ "name": "txt_baslik",
42
+ "type": "text",
43
+ "kind": "component",
44
+ "props": {
45
+ "content": {
46
+ "type": "static",
47
+ "ref": null,
48
+ "data": "Siparişler"
49
+ },
50
+ "type": {
51
+ "type": "static",
52
+ "ref": null,
53
+ "data": "h2"
54
+ }
55
+ },
56
+ "appearance": {
57
+ "marginType": "none",
58
+ "widthType": "fill"
59
+ },
60
+ "blocks": [],
61
+ "events": [],
62
+ "validations": [],
63
+ "stateBindings": [],
64
+ "children": []
65
+ }
66
+ },
67
+ {
68
+ "op": "add_component",
69
+ "parent": "page_container",
70
+ "component": {
71
+ "elementId": "txt_guncelleme",
72
+ "name": "txt_guncelleme",
73
+ "type": "text",
74
+ "kind": "component",
75
+ "props": {
76
+ "content": {
77
+ "type": "static",
78
+ "ref": null,
79
+ "data": ""
80
+ },
81
+ "type": {
82
+ "type": "static",
83
+ "ref": null,
84
+ "data": "small"
85
+ },
86
+ "isHidden": {
87
+ "type": "static",
88
+ "ref": null,
89
+ "data": true
90
+ }
91
+ },
92
+ "appearance": {
93
+ "marginType": "none",
94
+ "widthType": "fill"
95
+ },
96
+ "blocks": [],
97
+ "events": [],
98
+ "validations": [],
99
+ "stateBindings": [],
100
+ "children": []
101
+ }
102
+ },
103
+ {
104
+ "op": "add_component",
105
+ "parent": "page_container",
106
+ "component": {
107
+ "elementId": "scr_siparis",
108
+ "name": "scr_siparis",
109
+ "type": "scroll-view",
110
+ "kind": "component",
111
+ "props": {
112
+ "refreshable": {
113
+ "type": "static",
114
+ "ref": null,
115
+ "data": true
116
+ }
117
+ },
118
+ "appearance": {
119
+ "marginType": "none",
120
+ "widthType": "fill",
121
+ "heightType": "fill"
122
+ },
123
+ "blocks": [],
124
+ "events": [],
125
+ "validations": [],
126
+ "stateBindings": [],
127
+ "children": []
128
+ }
129
+ },
130
+ {
131
+ "op": "add_component",
132
+ "parent": "scr_siparis",
133
+ "component": {
134
+ "elementId": "lst_siparis",
135
+ "name": "lst_siparis",
136
+ "type": "repeater",
137
+ "kind": "component",
138
+ "props": {
139
+ "rows": {
140
+ "type": "static",
141
+ "ref": null,
142
+ "data": []
143
+ }
144
+ },
145
+ "appearance": {
146
+ "marginType": "none",
147
+ "widthType": "fill"
148
+ },
149
+ "blocks": [],
150
+ "events": [],
151
+ "validations": [],
152
+ "stateBindings": [],
153
+ "children": [
154
+ {
155
+ "elementId": "lst_siparis_satir",
156
+ "name": "lst_siparis_satir",
157
+ "type": "view",
158
+ "kind": "component",
159
+ "props": {},
160
+ "blocks": [],
161
+ "children": [
162
+ {
163
+ "elementId": "row_siparis",
164
+ "name": "row_siparis",
165
+ "type": "card",
166
+ "kind": "component",
167
+ "props": {
168
+ "title": {
169
+ "type": "code",
170
+ "ref": "{{ item.no }}"
171
+ },
172
+ "description": {
173
+ "type": "code",
174
+ "ref": "{{ item.durum }}"
175
+ }
176
+ },
177
+ "appearance": {
178
+ "marginType": "none",
179
+ "widthType": "fill"
180
+ },
181
+ "blocks": [],
182
+ "events": [],
183
+ "validations": [],
184
+ "stateBindings": [],
185
+ "children": []
186
+ }
187
+ ]
188
+ },
189
+ {
190
+ "elementId": "lst_siparis_bos_gorunum",
191
+ "name": "lst_siparis_bos_gorunum",
192
+ "type": "view",
193
+ "kind": "component",
194
+ "props": {},
195
+ "blocks": [],
196
+ "children": [
197
+ {
198
+ "elementId": "lst_siparis_bos",
199
+ "name": "lst_siparis_bos",
200
+ "type": "emptystate",
201
+ "kind": "component",
202
+ "props": {
203
+ "title": {
204
+ "type": "static",
205
+ "data": "Sipariş yok"
206
+ }
207
+ },
208
+ "blocks": [],
209
+ "children": []
210
+ }
211
+ ]
212
+ }
213
+ ]
214
+ }
215
+ }
216
+ ],
217
+ "mountFlowCreateArgs": {
218
+ "host": "page",
219
+ "hostId": "<siparisler sayfa id>",
220
+ "name": "siparisler mount",
221
+ "slug": "siparisler-mount",
222
+ "steps": [
223
+ {
224
+ "stepId": "start",
225
+ "name": "start",
226
+ "type": "start",
227
+ "kind": "normal",
228
+ "args": {},
229
+ "branches": [
230
+ {
231
+ "id": "<GUID>",
232
+ "name": "always",
233
+ "nextStepId": "load_orders"
234
+ }
235
+ ]
236
+ },
237
+ {
238
+ "stepId": "load_orders",
239
+ "name": "load_orders",
240
+ "type": "microservice",
241
+ "kind": "normal",
242
+ "args": {
243
+ "microserviceId": "<sipariş listesi GET microservice id>",
244
+ "isRaw": false,
245
+ "parameters": []
246
+ },
247
+ "branches": [
248
+ {
249
+ "id": "<GUID>",
250
+ "name": "success",
251
+ "nextStepId": "apply_rows"
252
+ },
253
+ {
254
+ "id": "<GUID>",
255
+ "name": "error",
256
+ "nextStepId": "warn_offline"
257
+ }
258
+ ]
259
+ },
260
+ {
261
+ "stepId": "apply_rows",
262
+ "name": "apply_rows",
263
+ "type": "setter",
264
+ "kind": "normal",
265
+ "args": {
266
+ "iteratees": [
267
+ {
268
+ "id": "<GUID>",
269
+ "type": "change",
270
+ "source": "component",
271
+ "element": "lst_siparis",
272
+ "property": "rows",
273
+ "mapping": {
274
+ "source": "",
275
+ "mappings": {}
276
+ },
277
+ "value": {
278
+ "type": "code",
279
+ "ref": "Steps.load_orders.data"
280
+ }
281
+ },
282
+ {
283
+ "id": "<GUID>",
284
+ "type": "change",
285
+ "source": "component",
286
+ "element": "txt_guncelleme",
287
+ "property": "content",
288
+ "mapping": {
289
+ "source": "",
290
+ "mappings": {}
291
+ },
292
+ "value": {
293
+ "type": "code",
294
+ "ref": "Steps.load_orders.cache ? `Son güncelleme: ${new Date(Steps.load_orders.cache.at).toLocaleString()}` : ''"
295
+ }
296
+ },
297
+ {
298
+ "id": "<GUID>",
299
+ "type": "change",
300
+ "source": "component",
301
+ "element": "txt_guncelleme",
302
+ "property": "isHidden",
303
+ "mapping": {
304
+ "source": "",
305
+ "mappings": {}
306
+ },
307
+ "value": {
308
+ "type": "code",
309
+ "ref": "!Steps.load_orders.cache"
310
+ }
311
+ }
312
+ ]
313
+ },
314
+ "branches": [
315
+ {
316
+ "id": "<GUID>",
317
+ "name": "always",
318
+ "nextStepId": null
319
+ }
320
+ ]
321
+ },
322
+ {
323
+ "stepId": "warn_offline",
324
+ "name": "warn_offline",
325
+ "type": "alert",
326
+ "kind": "normal",
327
+ "args": {
328
+ "message": "{{ Steps.load_orders.error?.network ? 'Bağlantı yok ve bu liste daha önce açılmamış' : 'Siparişler yüklenemedi' }}",
329
+ "alertType": "info"
330
+ },
331
+ "branches": [
332
+ {
333
+ "id": "<GUID>",
334
+ "name": "always",
335
+ "nextStepId": null
336
+ }
337
+ ]
338
+ }
339
+ ]
340
+ },
341
+ "bindOps": [
342
+ {
343
+ "op": "set_event",
344
+ "target": "page_container",
345
+ "event": "mount",
346
+ "eventValue": "<mount akış id>"
347
+ },
348
+ {
349
+ "op": "set_event",
350
+ "target": "scr_siparis",
351
+ "event": "refresh",
352
+ "eventValue": "<mount akış id>"
353
+ }
354
+ ]
355
+ }
356
+ ]
357
+ }
@@ -0,0 +1,82 @@
1
+ {
2
+ "_meta": {
3
+ "synthetic": true,
4
+ "note": "Mobil uygulamada ağ yokken yapılan yazma (kök ADR 0022): bir dozu 'alındı' işaretleyen akış. Servis tanımı offlineWritable bildirir; akışın microservice adımında queued dalı bağlıdır. Ağ yokken çağrı cihazın kuyruğuna girer ve queued dalı 'bağlantı gelince işlenecek' der; bağlantı gelince kayıt aynı Idempotency-Key ile bir kez gönderilir. Akış mobil profilden ve kendi lint kurallarından bulgusuz geçer (test/mobile-rules.test.ts).",
5
+ "pairsWith": "mobile-offline-list (okuma tarafı: veri önbelleği ve error.network), mobile-list-detail-form (satırdan işlem)",
6
+ "sourceRef": "platform_guide('page') §Kural 1g-4; FLOW-043; MS-025; kök docs/adr/0022-mobil-yazma-kuyrugu.md; plans/mobil-cevrimdisi-calisma/00-sozlesme.md"
7
+ },
8
+ "_rules": {
9
+ "declare": "Servis tarafı: microservice_patch ile /offlineWritable = true. Anlamı 'aynı istek iki kez gelirse bir kez uygulanır, çakışmada son yazan kazanır'. Okuyan uca (tablo get/count, görünüm) verilirse MS-025 uyarır.",
10
+ "branch": "Akış tarafı: microservice adımına queued dalı (yalnız mobil). Ağ hatasında (yanıt hiç yok) dal bağlıysa, yöntem yazansa ve dosya parametresi yoksa çağrı kuyruğa girer ve queued koşar: Steps.<ad>.data null, Steps.<ad>.queued = { clientRequestId, at }. Dal bağlı değilse adım bugünkü gibi error'a gider. HTTP hatası (4xx, 5xx, önündeki vekil sunucunun 502'si) kuyruğa girmez.",
11
+ "serverDecides": "Cihaz servisin bildirimini bilmez — kararı sunucu verir. queued dalı bağlı yazmanın İLK isteği de Idempotency-Key taşır ve kuyruğa aynı kimlikle girer: yanıtı yolda kaybolan yazma kuyruktan gelince 200 { code: 'Duplicate' } alır, ikinci kez uygulanmaz. Bildirimsiz servis anahtarlı isteği 422 OfflineReplayNotAllowedException ile reddeder — adım çevrimiçiyken de her çağrıda error'a gider. Bildirimsiz servise queued bağlamayı FLOW-043 yazımda uyarır.",
12
+ "visibility": "Kaydın sonucu akışa dönmez. Bekleyen ve reddedilen sayısı Device.offline.queue = { pending, rejected }, reddedilenler Device.offline.rejected (id, service, at, status, code, message). Kod adımında Page.offline.list(), Page.offline.count(), Page.offline.sync() ve Page.offline.resolve(id, 'retry' | 'discard').",
13
+ "sync": "Gönderim: bağlantının dönüşü, uygulamanın ön plana dönüşü ve ağla soğuk açılış. Sıra kuyruğa giriş zamanı; tek tek; 4xx kaydı reddeder ve sıra sürer, oturum (401/403), 408, 429, 5xx ve ağ hatası sırayı durdurur.",
14
+ "limits": "Kuyruktaki kayda bağlı ikinci işlem yok: queued yolu Steps.<ad>.data okumaz (dönen kimliği bekleyen adım çevrimdışıyken koşamaz). Dosya parametreli çağrı kuyruğa girmez — adım error'a gider, Steps.<ad>.error.code = 'fileNotQueued'. Kayıt 7 günde gönderilemezse 'expired' ile reddedilir. Kayıtlar kullanıcıya bağlıdır: başkasının bekleyeni gönderilmez, çıkışta silinmez.",
15
+ "preview": "Studio önizlemesi ve web çalışma zamanında kuyruk yoktur (barındırıcı depo vermez): queued dalı hiç koşmaz. page_screenshot ağı kesemez; davranış yalnız cihazda görülür. Açık ekran ilk çizildiği kopyayla kalır: akışa eklenen queued dalı ekran bir sonraki açılışta etkindir.",
16
+ "verify": "microservice_get ile offlineWritable, flow_get ile queued dalı okunur. Cihazda: bağlantıyı kes, dozu işaretle (queued dalı koşar), bağlantıyı aç; row_list'te tek kayıt."
17
+ },
18
+ "microservicePatchArgs": {
19
+ "applicationId": "<uygulama id ya da slug>",
20
+ "id": "<doz-isle microservice id>",
21
+ "operations": [{ "op": "add", "path": "/offlineWritable", "value": true }]
22
+ },
23
+ "flowCreateArgs": {
24
+ "host": "page",
25
+ "hostId": "<bugun sayfa id>",
26
+ "name": "doz-al",
27
+ "slug": "doz-al",
28
+ "steps": [
29
+ {
30
+ "stepId": "start",
31
+ "name": "start",
32
+ "type": "start",
33
+ "kind": "normal",
34
+ "args": {},
35
+ "branches": [{ "id": "<GUID>", "name": "always", "nextStepId": "al" }]
36
+ },
37
+ {
38
+ "stepId": "al",
39
+ "name": "al",
40
+ "type": "microservice",
41
+ "kind": "normal",
42
+ "args": {
43
+ "microserviceId": "<doz-isle microservice id>",
44
+ "isRaw": false,
45
+ "parameters": [
46
+ { "name": "dozSaatiId", "type": "text", "source": "body", "value": { "type": "code", "ref": "Row.dozSaatiId" } },
47
+ { "name": "durum", "type": "text", "source": "body", "value": { "type": "code", "ref": "'alindi'" } }
48
+ ]
49
+ },
50
+ "branches": [
51
+ { "id": "<GUID>", "name": "always", "nextStepId": "tamam" },
52
+ { "id": "<GUID>", "name": "error", "nextStepId": "hata" },
53
+ { "id": "<GUID>", "name": "queued", "nextStepId": "kuyrukta" }
54
+ ]
55
+ },
56
+ {
57
+ "stepId": "tamam",
58
+ "name": "tamam",
59
+ "type": "alert",
60
+ "kind": "normal",
61
+ "args": { "message": "Doz kaydedildi", "alertType": "success" },
62
+ "branches": [{ "id": "<GUID>", "name": "always", "nextStepId": null }]
63
+ },
64
+ {
65
+ "stepId": "hata",
66
+ "name": "hata",
67
+ "type": "alert",
68
+ "kind": "normal",
69
+ "args": { "message": "{{ Steps.al.error?.code === 'fileNotQueued' ? 'Dosyalı işlem bağlantı gerektirir' : 'Doz kaydedilemedi' }}", "alertType": "error" },
70
+ "branches": [{ "id": "<GUID>", "name": "always", "nextStepId": null }]
71
+ },
72
+ {
73
+ "stepId": "kuyrukta",
74
+ "name": "kuyrukta",
75
+ "type": "alert",
76
+ "kind": "normal",
77
+ "args": { "message": "{{ Steps.al.queued ? 'Doz kuyruğa alındı — bağlantı gelince işlenecek' : '' }}", "alertType": "info" },
78
+ "branches": [{ "id": "<GUID>", "name": "always", "nextStepId": null }]
79
+ }
80
+ ]
81
+ }
82
+ }