@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.
- package/dist/knowledge/mobileComponentCatalog.js +1 -1
- package/dist/knowledge/platform/lint.js +23 -0
- package/dist/knowledge/platform/mobileOffline.js +45 -0
- package/dist/knowledge/platform/mobileRules.js +6 -0
- package/dist/knowledge/rules.js +25 -3
- package/dist/resources/examples/mobile-offline-list.json +357 -0
- package/dist/resources/examples/mobile-offline-queue.json +82 -0
- package/dist/resources/examples/mobile-offline-seamless.json +1520 -0
- package/dist/resources/guides/access.md +1 -1
- package/dist/resources/guides/expressions.md +1 -1
- package/dist/resources/guides/mobile-steps.md +4 -1
- package/dist/resources/guides/mobile.md +4 -0
- package/dist/resources/guides/page.md +111 -0
- package/dist/resources/guides/translation.md +2 -2
- package/dist/resources/schema-fingerprint.json +2 -2
- package/dist/resources/schemas/CreateMicroserviceCommand.json +3 -0
- package/dist/resources/schemas/MicroserviceDto.json +3 -0
- package/dist/resources/schemas/UpdateMicroserviceCommand.json +3 -0
- package/dist/tools/accessRuntime.js +1 -1
- package/dist/tools/applications.js +16 -2
- package/dist/tools/flows.js +89 -4
- package/package.json +1 -1
|
@@ -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
|
|
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
|
/**
|
package/dist/knowledge/rules.js
CHANGED
|
@@ -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
|
+
}
|